private void UpdateState(TypingRealm.World.WorldState state)
    {
        lock (_updateStateLock)
        {
            if (state.LocationId != _currentState.CurrentLocation?.LocationId)
            {
                // Very crude implementation, goes to API all the time and blocks the thread.
                var locationData = _locationsClient.GetLocationAsync(state.LocationId, default)
                                   .GetAwaiter().GetResult();

                // Update current location details only when moving to another location.
                _currentState.CurrentLocation = new LocationInfo(
                    state.LocationId,
                    locationData.Name,
                    locationData.Description);

                /*"TODO: get location name from cache",
                 * "TODO: get location description from cache");*/
            }

            if (state.AllowedActivityTypes.Contains(ActivityType.RopeWar) &&
                _currentState.CreateRopeWarTyper == null)
            {
                _currentState.CreateRopeWarTyper = _typerPool.MakeUniqueTyper(Guid.NewGuid().ToString());
            }

            if (!state.AllowedActivityTypes.Contains(ActivityType.RopeWar) &&
                _currentState.CreateRopeWarTyper != null)
            {
                _typerPool.RemoveTyper(_currentState.CreateRopeWarTyper);
                _currentState.CreateRopeWarTyper = null;
            }

            // TODO: Dispose all previous location entrances - sync up like at character selection screen.
            _currentState.LocationEntrances = new HashSet <LocationEntrance>(state.Locations.Select(
                                                                                 x => new LocationEntrance(x, GetLocationName(x), _typerPool.MakeUniqueTyper(Guid.NewGuid().ToString()))));

            // TODO: Dispose all previous things - sync up like at character selection screen.
            _currentState.RopeWars = new HashSet <RopeWarInfo>(state.RopeWarActivities.Select(
                                                                   x => new RopeWarInfo(x.ActivityId, x.ActivityId, x.Bet, _typerPool.MakeUniqueTyper(Guid.NewGuid().ToString()))));

            var characterId    = _characterId;
            var currentRopeWar = state.RopeWarActivities.FirstOrDefault(rw => rw.LeftSideParticipants.Contains(characterId) || rw.RightSideParticipants.Contains(characterId));

            if (currentRopeWar != null)
            {
                _currentState.CurrentRopeWar = new JoinedRopeWar(
                    _currentState.RopeWars.First(x => x.RopeWarId == currentRopeWar.ActivityId), _typerPool.MakeUniqueTyper(Guid.NewGuid().ToString()), _typerPool.MakeUniqueTyper(Guid.NewGuid().ToString()));
            }
            // TODO: Clean up these typers. When current rope war becomes null - we need to delete them from the typer pool.
            else
            {
                _currentState.CurrentRopeWar = null;
            }

            _stateSubject.OnNext(_currentState);
        }
    }
    public static void RemoveTyper(this ITyperPool typerPool, string id)
    {
        var typer = typerPool.GetByKey(id);

        if (typer == null)
        {
            return;
        }

        typerPool.RemoveTyper(typer);
    }
    public void RemoveSelectCharacterTyper(string characterId)
    {
        var character = Characters.SingleOrDefault(c => c.CharacterId == characterId);

        if (character == null)
        {
            return;
        }

        _characters.Remove(character);
        _typerPool.RemoveTyper(character.Typer);
    }