private EasyDecal PoolInstantiation(GameObject decalPrefab, GameObject parent, Vector3 position, Quaternion rotation)
    {
        var entity = PoolFactory.Spawn(decalPrefab, position, rotation, parent ? parent.transform : null);
        var decal  = entity.GetComponent <EasyDecal>();

#if UNITY_EDITOR
        if (decal.Lifetime < 1)
        {
            Debug.LogWarning("Do not set the life time too short, this may affect the fade out !");
        }
#endif

        decal.DontDestroy = true;
        decal.Reset(true);
        decal.LateBake();
        decal.OnFadedOut += OnFadedOut;

        decalDict.Add(decal, entity);
        return(decal);
    }
    public override void OnEnable()
    {
        base.OnEnable();

        blockSetupers.OnAdd().Subscribe(entity =>
        {
            var viewComponent = entity.GetComponent <ViewComponent>();
            var blockSetuper  = entity.GetComponent <BlockSetuper>();
            var counter       = new List <Dictionary <GameObject, int> >();
            var prefabs       = new List <GameObject>();
            var blocks        = new List <GameObject>();
            var maximum       = new Dictionary <GameObject, int>();
            var disposer      = new CompositeDisposable();

            foreach (var block in blockSetuper.Setup.Blocks)
            {
                var subCounter = new Dictionary <GameObject, int>();
                foreach (var kvp in block.ToDictionary())
                {
                    if (!subCounter.ContainsKey(kvp.Value.GameObject))
                    {
                        subCounter.Add(kvp.Value.GameObject, 1);
                    }
                    else
                    {
                        subCounter[kvp.Value.GameObject]++;
                    }
                    if (!prefabs.Contains(kvp.Value.GameObject))
                    {
                        prefabs.Add(kvp.Value.GameObject);
                    }
                }
                counter.Add(subCounter);
            }

            foreach (var prefab in prefabs)
            {
                var max = 0;
                foreach (var c in counter)
                {
                    if (c.ContainsKey(prefab) && c[prefab] > max)
                    {
                        max = c[prefab];
                    }
                }

                maximum.Add(prefab, max);
            }

            viewComponent.Transforms[0].OnActiveAsObservable().Subscribe(_ =>
            {
                foreach (var prefab in prefabs)
                {
                    for (int i = 0; i < blockSetuper.MaxVisibleCount * maximum[prefab]; i++)
                    {
                        PoolFactory.Despawn(PoolFactory.Spawn(prefab, transform));
                    }
                }

                int index = 0;
                blocks.Add(CreateBlock(blockSetuper.Setup.Blocks[index], viewComponent.Transforms[0]));

                EventSystem.OnEvent <RequestBlockProcessingEvent>().Subscribe(evt =>
                {
                    index++;
                    if (index < blockSetuper.Setup.Blocks.Count)
                    {
                        var block = CreateBlock(blockSetuper.Setup.Blocks[index], viewComponent.Transforms[0]);

                        block.transform.position = new Vector3(0, 0, 50 * index);
                        blocks.Add(block);
                    }
                    while (blocks.Count > blockSetuper.MaxVisibleCount)
                    {
                        RemoveBlock(blocks[0]);
                        blocks.RemoveAt(0);
                    }
                }).AddTo(this.Disposer).AddTo(blockSetuper.Disposer).AddTo(disposer);
            }).AddTo(this.Disposer).AddTo(blockSetuper.Disposer);

            viewComponent.Transforms[0].OnInactiveAsObservable().Subscribe(_ =>
            {
                disposer.Clear();
                foreach (var block in blocks)
                {
                    RemoveBlock(block);
                }
            }).AddTo(this.Disposer).AddTo(blockSetuper.Disposer);
        }).AddTo(this.Disposer);
    }
    public override void OnEnable()
    {
        base.OnEnable();
        ShootComponents.OnAdd().Subscribe(entity =>
        {
            var networkIdentityComponent = entity.GetComponent <NetworkIdentityComponent>();
            var shootComponent           = entity.GetComponent <ShootComponent>();

            shootComponent.WeaponIndex.DistinctUntilChanged().Subscribe(index =>
            {
                if (index >= 0 && index < shootComponent.Weapons.Count)
                {
                    var name   = shootComponent.Weapons[index];
                    var path   = WeaponDAO.GetPath(name);
                    var prefab = Resources.Load <GameObject>(path);
                    var bullet = WeaponDAO.GetBullet(name);

                    if (shootComponent.CurrentWeaponEntity != null)
                    {
                        PoolFactory.Despawn(shootComponent.CurrentWeaponEntity);
                    }

                    shootComponent.CurrentWeaponEntity   = PoolFactory.Spawn(prefab, shootComponent.Parent);
                    shootComponent.bulletPrefab          = Resources.Load <GameObject>(BulletDAO.GetPath(bullet));
                    shootComponent.muzzleFlashesPrefab   = Resources.Load <GameObject>(WeaponDAO.GetMuzzleFlashesEffectPath(name));
                    shootComponent.adsPosition           = WeaponDAO.GetADSPosition(name);
                    shootComponent.bulletLocalPosition   = WeaponDAO.GetBulletSpawnPosition(name);
                    shootComponent.muzzleFlashesPosition = WeaponDAO.GetMuzzleFlashesPosition(name);
                    shootComponent.holeSize = BulletDAO.GetHoleSize(bullet);
                    shootComponent.speed    = WeaponDAO.GetSpeed(name);
                    shootComponent.cooldown = WeaponDAO.GetCooldown(name);

                    if (shootComponent.CurrentWeaponEntity != null)
                    {
                        var weaponViewComponent = shootComponent.CurrentWeaponEntity.GetComponent <ViewComponent>();

                        weaponViewComponent.Transforms[0].localPosition = WeaponDAO.GetPosition(name);
                        shootComponent.weaponLocalRotation = Quaternion.Euler(new Vector3(0, 90, 90));
                        weaponViewComponent.Transforms[0].localRotation = shootComponent.weaponLocalRotation;
                    }
                }
                else
                {
                    shootComponent.WeaponIndex.Value = Mathf.Clamp(index, 0, shootComponent.Weapons.Count);
                }
            }).AddTo(this.Disposer).AddTo(shootComponent.Disposer);

            for (int i = 0; i < shootComponent.Weapons.Count; i++)
            {
                var path   = BulletDAO.GetPath(WeaponDAO.GetBullet(shootComponent.Weapons[i]));
                var prefab = Resources.Load <GameObject>(path);
                for (int j = 0; j < WarmupBullets; j++)
                {
                    PoolFactory.Despawn(PoolFactory.Spawn(prefab, networkIdentityComponent.Identity.UserId, 0));
                }
            }
        }).AddTo(this.Disposer);

        NetwrokTimeline.OnForward(data =>
        {
            var networkIdentityComponent = data.Entity.GetComponent <NetworkIdentityComponent>();
            var playerControlComponent   = data.Entity.GetComponent <PlayerControlComponent>();
            var shootComponent           = data.Entity.GetComponent <ShootComponent>();
            var animator            = data.Entity.GetComponent <Animator>();
            var weaponViewComponent = shootComponent.CurrentWeaponEntity != null ? shootComponent.CurrentWeaponEntity.GetComponent <ViewComponent>() : null;
            var userInputData       = data.UserInputData[0];
            var mouseInput          = userInputData[0].GetInput <MouseInput>();
            var keyInput            = userInputData[1].GetInput <KeyInput>();
            var eventInputs         = userInputData[2].GetInputs <EventInput>();

            EventInput eventInput = null;

            for (int i = 0; i < eventInputs.Length; i++)
            {
                if (eventInputs[i].Type == EventCode.PlayerCamera)
                {
                    eventInput = eventInputs[i];
                    break;
                }
            }

            if (mouseInput != null && keyInput != null)
            {
                if (playerControlComponent.Aim.Value == AimMode.Free && keyInput.KeyCodes.Contains((int)KeyCode.LeftAlt))
                {
                }
                else if (weaponViewComponent != null && eventInput != null)
                {
                    // The point hit by the muzzle is different from the point hit in the center of the screen.
                    // So how to solve this problem, my solution is like this:

                    // First, get the hit point in the center of the screen.
                    // 0 - camera position, 1 - camera dirstion, 2 - camera far clip plane.
                    RaycastHit hit;
                    var ray   = new Ray((Vector3)eventInput.Get <FixVector3>(0), (Vector3)eventInput.Get <FixVector3>(1));
                    var point = Physics.Raycast(ray, out hit, (float)eventInput.Get <Fix64>(2)) ? hit.point : ray.origin + (float)eventInput.Get <Fix64>(2) * ray.direction;

                    // Reset rotation to default rotation
                    weaponViewComponent.Transforms[0].localRotation = shootComponent.weaponLocalRotation;

                    // If not in aim down sight mode, then I can fine-tune the angle of the gun to make the direction of
                    // the bullet closer to the point at the center of the screen
                    if (playerControlComponent.Aim.Value != AimMode.AimDownSight)
                    {
                        var targetDirection = (point - weaponViewComponent.Transforms[0].TransformPoint(shootComponent.bulletLocalPosition)).normalized;
                        var angle           = Vector3.Angle(weaponViewComponent.Transforms[0].forward, targetDirection);
                        var t        = angle == 0 ? 1 : Mathf.Clamp01(shootComponent.LimitAngle / angle);
                        var rotation = Quaternion.LookRotation(Vector3.Lerp(weaponViewComponent.Transforms[0].forward, targetDirection, t));

                        weaponViewComponent.Transforms[0].rotation = rotation;
                    }
                }

                animator.SetBool(Shoot_b, mouseInput.MouseButtons.Contains(0));

                if (mouseInput.MouseButtons.Contains(0) && shootComponent.cooldownTime <= 0)
                {
                    var entity              = PoolFactory.Spawn(shootComponent.bulletPrefab, networkIdentityComponent.Identity.UserId, data.TickId);
                    var bulletComponent     = entity.GetComponent <BulletComponent>();
                    var bulletViewComponent = entity.GetComponent <ViewComponent>();

                    if (weaponViewComponent != null)
                    {
                        bulletViewComponent.Transforms[0].position = weaponViewComponent.Transforms[0].TransformPoint(shootComponent.bulletLocalPosition);
                        bulletViewComponent.Transforms[0].rotation = Quaternion.LookRotation(weaponViewComponent.Transforms[0].forward, weaponViewComponent.Transforms[0].up);
                        bulletComponent.velocity = shootComponent.speed * (FixVector3)bulletViewComponent.Transforms[0].forward;
                        bulletComponent.holeSize = shootComponent.holeSize;

                        StartCoroutine(AsyncMuzzleFlashes(shootComponent.muzzleFlashesPrefab, weaponViewComponent.Transforms[0].TransformPoint(shootComponent.muzzleFlashesPosition), weaponViewComponent.Transforms[0].rotation));
                    }

                    shootComponent.cooldownTime = shootComponent.cooldown;
                }
            }
            shootComponent.cooldownTime -= data.DeltaTime;

            return(null);
        }).AddTo(this.Disposer);
    }