Rust что значит no bps
Чего не стоит делать в Rust, если начали играть в 2021 году
Rust – это необычный симулятор выживания, который привлек к себе внимание огромное количество геймеров. При этом новички часто думают, что в этом проекте нет ничего сложного, и уже с самого начала делают все то, что и в других играх с элементами выживания.
К сожалению, Rust не отличается особым гостеприимством по отношению к новым игрокам, поэтому стартовать бывает довольно сложно. Перед вами подборка главных ошибок, которые делают новички, решившие поиграть в Rust в 2021 году.
Одному будет тяжело
Rust – далеко не самая лучшая многопользовательская игра для одного человека. Здесь есть несколько этапов развития, и добраться до каждого из них можно только за счет продолжительного гринда. Если играть в команде со своими друзьями, то вы гораздо быстрее достигните цели, чем в одиночку.
Также стоит отметить, что 99% других игроков не дадут вам мирно существовать в виртуальном мире игры. Вам постоянно придется отбиваться от обезумивших «дикарей», которые захотят отобрать ваши вещи и ресурсы. Естественно, ни у одного новичка не получится защитить себя от оравы более опытных игроков, поэтому лучше изначально залетать в Rust хотя бы с парой друзей.
Никому нельзя верить
Этот пункт частично противоречит предыдущему, но при этом он еще более важен. Прежде всего вам стоит забыть о том, что взаимодействие с другими игроками в многопользовательских проектах – это норма. Rust вообще не та игра, где нужно объединяться с незнакомыми людьми, чтобы вместе получить больше лута или ресурсов. Здесь вы можете рассчитывать только на себя, и если начнете доверять первому встречному игроку, то очень скоро поймете, почему этого нельзя делать. Особенно это касается товарищей с хорошей экипировкой, которых вы встретите на своем пути.
Дело в том, что в Rust каждый играет сам за себя, а опытные игроки очень часто обманывают новичков самыми разными способами. Незнакомец, который предложит побегать с ним по виртуальному миру и при этом будет носить броню заметно лучше вашей, скорей всего грифер. Это такой игрок, который при первой же удобной возможности просто вас убьет и заберет все вещи. Так что, начиная играть в Rust, никому не доверяйте!
Курс юного строителя
Если вы вдруг не знали, то в Rust есть строительство, и здесь оно играет довольно важную роль. При этом данная механика имеет ряд особенностей, которые придется изучить в самом начале знакомства с игрой, иначе ваши архитектурные «шедевры» будут попросту разваливаться, а вы впустую потратите ценные ресурсы.
Прежде всего стоит отметить, что у каждого строительного блока есть мягкая и твердая сторона. Во время строительства блок всегда нужно устанавливать таким образом, чтобы твердая сторона находилась снаружи будущего здания. Если не соблюдать это правило и размещать материалы как попало, то вашу постройку сможет развалить первый попавшийся игрок, причем с помощью обычного топора или кирки. Согласитесь, будет не очень приятно наблюдать за тем, как несколько часов ваших трудов кто-то разбирает по кирпичикам за считаные минуты.
Все вещи в одном месте
Огромное количество игроков в Rust вообще не уделяют время крафту. Они считают, что гораздо проще украсть готовые предметы у других пользователей, чем стоять у станка и пытаться что-то сделать. Именно поэтому в этой игре противопоказано хранить все свои вещи в одном месте.
Ни в коем случае не размещайте абсолютно все запасы на единственной базе, да еще и в конкретном помещении. В таком случае после случайного налета кучки любителей халявы вы потеряете абсолютно все. Конечно, вряд ли у новичка хватит ресурсов, чтобы построить себе 4-5 домов и правильно распределить по ним ценные предметы, но хотя бы попробуйте сделать что-то подобное. Неплохим решением будет на территории одной базы построить несколько «нычек» и распределить по ним ресурсы и предметы.
Не забывайте про аптечки
Если вы решите, что аптечки вам не нужны и со своим крутым автоматом вы сможете одолеть кого угодно, то Rust очень быстро вас разочарует. Здесь очень просто погибнуть, и иногда вы даже не будете понимать, почему это вообще произошло. В результате игрок, у которого было полно аптечек, просто завалит вас рандомной палкой и заберет тот самый крутой автомат.
Поставьте себе домофон
Если же вы не можете сделать кодовый замок или уже поставили везде обычные двери, то не делайте ключ. Пускай доступ к зданию будет только у вас. Отсутствие ключа гарантировано защитит ваши владения, даже если вы внезапно погибнете.
Не используйте факел
Дело в том, что свет от факела моментально привлечет к вам внимание других игроков. Часть из них будет гриферами, которые быстро прибегут на ваш «сигнал» и просто убьют. На этом ваш многообещающий забег в Rust просто закончится и придется начинать все сначала. Первое время лучше бегайте без факела и пытайтесь ориентироваться на карте с помощью своего зрения.
Вы всегда в опасности
Многие новички ошибочно думают, что после того, как они построят себе укрытие и обзаведутся хоть какой-то экипировкой, можно просто расслабиться и наслаждаться игровым процессом Rust. Этот проект не об этом, вы всегда будете под прицелом у других игроков! Причем если у вас вдруг все слишком хорошо и на это обратят внимание остальные пользователи игровой сессии, то очень скоро вас ждет набег незваных гостей.
Перестрелка – не самая лучшая идея
Некоторые новички в Rust почему-то считают, что это экшен-шутер, в котором прямо-таки необходимо ввязываться в перестрелки и каждую минуту показывать, кто здесь круче. На самом деле проект про выживание, и я вам гарантирую, что ваша беготня с автоматом закончится очень быстро, если вы вдруг решите, что можете держать всю карту в страхе.
Вот такие советы мы решили дать новичкам, которые только надумали залететь в Rust! Делая все эти вещи, вы гарантировано проживете в виртуальном мире игры чуточку дольше и при этом гораздо лучше узнаете все тонкости проекта. Главное, не забывайте всегда быть начеку, здесь нет зоны комфорта.
Rust что значит no bps
Ну а теперь, давайте разбираться, что делать, когда Раст лагает и фризит на мощном ПК?
Для начала, предположим, что ваш компьютер соответствует всем требованиям и абсолютно исправен, но игра все-равно периодически лагает, особенно при стрельбе, полетах на коптере и открытии инвентаря:
Ну или просто фризит в высоконагруженных локациях вроде лагеря бандитов:
В этом случае вам может помочь консольная команда gc.buffer. Именно об этой команде я уже снимал видео и не буду сейчас детально описывать принцип её действия. Если вам интересны подробности, можете посмотреть то видео:
Итак, в зависимости от объёма оперативной памяти, вводим в консоль или добавляем в параметры запуска следующую команду:
И наслаждаемся игрой без лагов! Метод проверенный и помог уже сотням и тысячам растеров! Только не забудьте, что если вы вводите команду через консоль, а не в параметрах запуска, то делать это придётся при каждом входе в игру
Следующая возможная причина лагов — это жёсткий диск. Раст это очень нагруженная текстурами и объектами игра, которая постоянно оперирует огромным количеством данных, задействуя при этом жесткий диск вашего ПК и, если жёсткий диск не справляется, вы будете наблюдать лаги в игре.
Чтобы решить эту проблему, в первую очередь убедитесь, что в вашем компьютере установлен современный SSD диск, а не древний и медленный HDD.
Кстати говоря, даже в минимальных системных требованиях для Раста указано, что SSD диск крайне рекомендуется!
Также не лишним будет проверить состояние вашего диска, поскольку они тоже имеют свойство изнашиваться и терять скорость. Для проверки можно использовать любую из сотен предназначенных для этого программ, ну например MHDD [www.mhdd.ru] или Victoria [hdd.by]
Если проверка показывает наличие ошибок или значительное снижение скорости, то пора задуматься о замене диска!
Третья причина фризов в Расте — это оперативная память. Притом речь сейчас идёт не о её объёме, а о том, как она работает.
Возможно, для многих это будет открытием, но оперативка тоже может работать с ошибками и выходить из строя, притом зачастую это может происходить практически незаметно для пользователя. Я уже несколько раз наблюдал ситуацию, когда у моих подписчиков Rust лагал именно из-за ошибок в работе оперативной памяти, более того, я сам недавно с этим столкнулся, когда на стриме у меня резко снизился фпс и игра стала какой-то дерганой.
Для проверки работы памяти на вашем компьютере я могу порекомендовать бесплатную утилиту OCCT [www.ocbase.com] Кстати, этой же программой можно проверить ваш процессор и убедиться, что он тоже работает без ошибок и не перегревается. Прогоните в ней тест памяти, и если программа найдёт ошибки, то попробуйте почистить контакты модулей памяти спиртом и поменять их местами в слотах на материнской плате, а если не помогло, то снижайте частоту памяти в BIOS.
Только очень вас прошу, если вы не разбираетесь в компьютерах, то лучше попросите кого-нибудь из знакомых вам помочь, или вызовите мастера!
Если же все эти манипуляции не помогают и тест всё равно выявляет ошибки – оперативку пора менять
Ну и последняя причина, из-за которой Раст частенько лагает – это интернет, а точнее стабильность его работы. Бывает такое, что всё вроде бы нормально и проблем при работе с браузером вы не замечаете, да и при выборе сервера Раст показывает вполне нормальный пинг.
Но, в тоже время в игре вы можете наблюдать как вас периодически откидывает назад и фризит по несколько секунд. Эти проблемы в первую очередь указывают на нестабильную работу вашего интернета.
Чтобы убедиться в этом, достаточно ввести в консоль команду global.perf 4, которая отобразит в левом нижнем углу экрана различные показатели вашей игровой сессии и в том числе пинг:
Именно за этим параметром следует некоторое время понаблюдать. Если вы заметите, что пинг постоянно скачет в большом диапазоне, например от 50 поднимается до 500 и выше, а затем падает обратно, значит проблема и правда связана с каналом передачи данных.
В этом случае, попробуйте для начала сменить сервер, дабы убедиться, что проблема на вашей стороне. Если же на другом сервере картина никак не меняется, то звоните своему провайдеру, жалуйтесь на плохое интернет-соединение и нестабильный пинг и пускай они разбираются, вы им все-таки деньги за это платите! Также может оказаться, что дело в вашем роутере, в этом случае техники провайдера сообщат вам о необходимости его замены.
Без стабильного интернет-соединения вы будете наблюдать постоянные фризы в игре!
Ну что, вот и все основные причины, по которым Rust часто лагает, фризит или выдаёт низкий фпс. По крайней мере, именно с этими проблемами сталкивалось большинство моих подписчиков, обращавшихся ко мне за помощью. Однако, не стоит забывать, что всё вышеописанное это не панацея! Может быть ещё множество причин и частных случаев, начиная от проблем с программным обеспечением и вирусами и заканчивая багами самой игры. К сожалению, рассказать обо всех возможных вариантах я просто физически не могу, иначе этот гайд был бы бесконечным.
Тем не менее, надеюсь, он помог вам решить проблемы с игрой, и если это так, то не забудьте пожалуйста поставить лайк, поделиться им с друзьями и написать в комментариях о своём опыте решения проблем с лагами в Расте!
На пальцах: ассоциированные типы в Rust и в чём их отличие от аргументов типов
Для чего в Rust есть ассоциированные типы (associated types), и в чём их отличие от аргументов типов (type arguments aka generics), ведь они так похожи? Разве недостаточно только последних, как во всех нормальных языках? У тех, кто только начинает изучать Rust, а особенно у людей, пришедших из других языков («Это же дженерики!» — скажет умудрённый годами джавист), такой вопрос возникает регулярно. Давайте разбираться.
TL;DR Первые контролирует вызываемый код, вторые — вызывающий.
Дженерики vs ассоциированные типы
Итак, у нас уже есть аргументы типов, или всеми любимые «дженерики». Выглядит это примерно так:
Здесь T как раз и есть аргумент типа. Вроде бы этого должно быть достаточно всем (как 640 килобайт памяти). Но в Rust же есть ещё и ассоциированные типы, примерно такие:
На первый взгляд те же яйца, но с другого ракурса. Зачем понадобилось вводить в язык ещё одну сущность? (Которой, кстати, в ранних версиях языка и не было.)
Здесь тип T передаётся вызывающей стороной как аргумент, даже если это происходит неявно (если компилятор выведет этот тип за вас). Иными словами, именно вызывающая сторона решает, каким новым типом T будет прикидываться наш тип, реализующий этот трейт:
В случае с ассоциированным типом всё с точностью до наоборот. Ассоциированный тип полностью контролируется тем, кто реализует данный трейт, а не вызывающей стороной.
Распространённый пример — итератор. Допустим, у нас есть коллекция, и мы хотим получить от неё итератор. Значения какого типа должен возвращать итератор? В точности того, который содержится в этой коллекции! Не вызывающая сторона должна решать, что вернёт итератор, а сам итератор лучше знает, что именно он умеет возвращать. Вот сокращённый код из стандартной библиотеки:
Заметьте, что у итератора нет параметра типа, который позволил бы вызывающей стороне выбрать, что должен вернуть итератор. Вместо этого, тип возвращаемого из метода next() значения определяется самим итератором с помощью ассоциированного типа, но при этом он не приколочен гвоздями, т.е. каждая реализация итератора может выбрать свой тип.
Стоп. Ну и что? Всё равно непонятно, чем это лучше дженерика. Представим на минутку, что мы используем обычный дженерик вместо ассоциированного типа. Трейт итератора тогда будет выглядеть как-то так:
Но теперь, во-первых, тип T нужно снова и снова указывать в каждом месте где упоминается итератор, а во-вторых, теперь стало возможно реализовать этот трейт несколько раз с разными типами, что для итератора выглядит как-то странно. Вот пример:
Если же вернуться к варианту с ассоциированным типом, то всех этих проблем можно избежать:
Для закрепления. Коллекция может реализовать вот такой трейт, чтобы уметь превращать себя в итератор:
И опять-таки, тут именно коллекция решает, какой это будет итератор, а именно: итератор, тип возвращаемого значения которого совпадает с типом элементов самой коллекции, и никакой другой.
Ещё более «на пальцах»
Если примеры выше всё равно непонятны, то вот ещё менее научное но более доходчивое объяснение. Аргументы типов можно рассматривать как «входную» информацию, которую мы предоставляем, чтобы трейт работал. Ассоциированные типы можно рассматривать как «выходную» информацию, которую трейт предоставляет нам, чтобы мы могли воспользоваться результатами его работы.
В стандартной библиотеке есть возможность перегружать для своих типов математические операторы (сложение, вычитание, умножение, деление и тому подобное). Для этого нужно реализовать один из соответствующих трейтов из стандартной библиотеки. Вот, например, как выглядит этот трейт для операции сложения (опять же, упрощённо):
Тут у нас есть «входной» аргумент RHS — это тип, к которому мы будем применять операцию сложения с нашим типом. И есть «выходной» аргумент Add::Output — это тот тип, который получится в результате сложения. В общем случае он может отличаться от типа слагаемых, которые, в свою очередь, тоже могут быть разных типов (к синему прибавить вкусное, и получить мягкое — а что, я так всё время делаю). Первый задан с помощью аргумента типа, второй — с помощью асоциированного типа.
Можно реализовать сколько угодно сложений с разными типами второго аргумента, но каждый раз тип результата будет только один, и он определяется реализацией этого сложения.
Попробуем реализовать этот трейт:
Итого
Используем дженерики там, где мы не против иметь несколько реализаций трейта для одного типа, и где приемлемо указывать конкретную реализацию на стороне вызова. Используем ассоциированные типы там, где мы хотим иметь одну «каноничную» реализацию, которая сама контролирует типы. Сочетаем и смешиваем в нужных пропорциях, как в последнем примере.
Провалилась монетка? Добейте меня комментариями.
Многопоточность в Rust
Rust начинался как проект, решающий две трудные проблемы:
С точки зрения безопасности работы с памятью это означает, что вы можете не использовать сборщик мусора и в то же время не опасаться сегфолтов, потому что Rust не даст вам совершить ошибку.
С точки зрения многопоточности это означает, что вы можете пользоваться различными парадигмами (передача сообщений, разделяемое состояние, lock-free-структуры данных, чистое функциональное программирование), и Rust позволит избежать наиболее распространённых подводных камней.
Цель этого поста — показать, как это делается.
Основы: владение данными
Мы начнём с обзора систем владения и заимствования данных в Rust. Если вы уже знакомы с ними, то вы можете пропустить обе части «основ» и перейти непосредственно к многопоточности. Если же вы захотите поглубже разобраться в этих концепциях, я очень рекомендую вот эту статью, написанную Yehuda Katz. В официальной книге Rust вы найдёте ещё более подробные объяснения.
В Rust у каждого значения есть «область владения», и передача или возврат значения означает передачу права владения («перемещение») в новую область. Когда область заканчивается, то все значения, которыми она владеет к этому моменту, уничтожаются.
Рассмотрим несколько простых примеров. Предположим, мы создаём вектор и помещаем в него несколько элементов:
Становится интереснее, если вектор передаётся в другую функцию или возвращается из функции:
Как только право владения значением передано куда-то ещё, его нельзя больше использовать. Например, рассмотрим такой вариант функции use_vec :
Если вы попробуете скомпилировать этот вариант, компилятор выдаст ошибку:
Компилятор сообщает, что vec больше недоступен — право владения передано куда-то ещё. И это очень хорошо, потому что к этому моменту вектор уже уничтожен.
Основы: заимствование
Пока что код получается не очень удобным, потому что нам не нужно, чтобы print_vec уничтожал вектор, который ему передаётся. На самом деле мы бы хотели предоставить print_vec временный доступ к вектору и иметь возможность продолжить его использовать впоследствии.
Здесь нам и понадобится заимствование. В Rust если у вас есть значение, вы можете дать временный доступ к нему функциям, которые вы вызываете. Rust автоматически проверит, что эти «займы» не будут действовать дольше, чем «живёт» объект, который заимствуется.
Чтобы позаимствовать значение, нужно создать ссылку на него (ссылка — один из видов указателей) при помощи оператора & :
Каждая ссылка действует только в определённой области видимости, которую компилятор определяет автоматически. Ссылки бывают двух видов.
Зачем нужны два вида ссылок? Рассмотрим функцию следующего вида:
Эта функция проходит по каждому элементу вектора, помещая их все в другой вектор. В итераторе (созданном методом iter() ) содержатся ссылки на вектор в текущей и конечной позициях, и текущая позиция «перемещается» в направлении конечной.
Что произойдёт, если мы вызовем эту функцию с одним и тем же вектором в обоих аргументах?
Это приведёт к катастрофе! Когда мы помещаем новые элементы в вектор, иногда ему потребуется изменить размер, для чего выделяется новый участок памяти, в который копируются все элементы. В итераторе останется «висящая» ссылка в старую память, что приведёт к небезопасной работе с памятью, т.е. к segfault’ам или к чему-нибудь ещё похуже.
К счастью, Rust гарантирует, что пока существует мутабельное заимствование, других ссылок на объект быть не может, и поэтому код выше приведёт к ошибке компиляции:
Передача сообщений
Теперь, после того, как мы кратко рассмотрели, что такое владение и заимствование, посмотрим, как эти концепции пригождаются в многопоточном программировании.
Существует множество подходов к написанию многопоточных программ, но один из наиболее простых из них — это передача сообщений, когда потоки или акторы общаются, отправляя друг другу сообщения. Сторонники этого стиля особенно обращают внимание на то, что он связывает совместное использование данных и общение между акторами:
Не общайтесь через совместный доступ к памяти; наоборот, обеспечивайте совместный доступ через общение.
— Effective Go
Владение данными в Rust позволяет очень легко преобразовать этот совет в правило, проверяемое компилятором. Рассмотрим такой API для работы с каналами (хотя каналы в стандартной библиотеке Rust немного отличаются):
Здесь поток создаёт вектор, отправляет его в другой поток и затем продолжает его использовать. Поток, получивший вектор, мог бы его изменить в то время, когда первый поток ещё работает, поэтому вызов print_vec мог бы привести к гонке или, например, ошибке типа use-after-free.
Вместо этого компилятор Rust выдаст ошибку на вызове print_vec :
Блокировки
Другой способ работы со многими потоками — это организация общения потоков через пассивное разделяемое состояние.
У многопоточности с разделяемым состоянием дурная слава. Очень легко забыть захватить блокировку или как-то ещё изменить не те данные не в то время, с катастрофичным результатом — настолько легко, что многие программисты отказываются от такого способа многопоточного программирования полностью.
Подход Rust заключается в следующем:
Потоки в Rust «изолированы» друг от друга автоматически благодаря концепции владения данными. Запись может происходить только тогда, когда у потока есть мутабельный доступ к данным: либо за счёт того, что поток ими владеет, либо за счёт наличия мутабельной ссылки. Так или иначе, гарантируется, что поток будет единственным, кто в данный момент времени может получить доступ к данным. Рассмотрим реализацию блокировок в Rust, чтобы понять, как это работает.
Вот упрощённая версия их API (вариант в стандартной библиотеке более эргономичен):
Этот интерфейс достаточно необычен в нескольких аспектах.
Здесь мы можем отметить два ключевых момента:
Компилятор Rust сгенерирует ошибку, в точности указывающую на проблему:
Потокобезопасность и трейт Send
Вполне логично разделять типы данных на те, которые являются «потокобезопасными», и те, которые не являются. Структуры данных, которые безопасно использовать из нескольких потоков, применяют инструменты для синхронизации внутри себя.
Например, вместе с Rust поставляется два типа «умных указателей», использующих подсчёт ссылок:
Обычный подход сводится к тщательной документации. В большинстве языков нет семантической разницы между потокобезопасными и небезопасными типами.
Таким образом, программисты на Rust могут пользоваться преимуществами Rc и других типов данных, небезопасных для использования в многопоточной среде, будучи уверенными, что если они попытаются случайно передать такие типы в другой поток, компилятор Rust сообщит:
Совместный доступ к стеку: scoped
До сих пор все структуры данных создавались на куче, которая затем использовалась из нескольких потоков. Но что если нам нужно запустить поток, который использует данные, «живущие» в стеке текущего потока? Это может быть опасно:
Чтобы избежать подобных проблем работы с памятью, основной API для запуска потоков в Rust выглядит примерно так:
Ограничение ‘static означает, грубо говоря, что в замыкании не должны использоваться заимствованные данные. В частности, это значит, что код, подобный parent выше, не скомпилируется:
По сути, это исключает возможность того, что стек parent может быть очищен, когда его ещё используют другие потоки. Катастрофа предотвращена.
Но есть и другой способ гарантировать безопасность: удостовериться, что родительский стек остаётся в порядке до тех пор, пока дочерний поток не завершится. Такой паттерн называется fork-join-программированием и часто применяется при разработке параллельных алгоритмов типа «разделяй и властвуй». Rust поддерживает этот подход с помощью специальной функции для запуска дочернего потока:
Поэтому вышеприведённый пример мы можем исправить следующим образом:
Таким образом, в Rust вы можете свободно использовать данные, размещённые на стеке, в дочерних потоках, будучи уверенными, что компилятор проверит наличие всех необходимых операций синхронизации.
Гонки данных
Теперь мы рассмотрели достаточно примеров, чтобы привести, наконец, довольно строгое утверждение о подходе Rust к многопоточности: компилятор предотвращает все гонки данных.
Гонка данных (data race) возникает при несинхронизированном обращении к данным из нескольких потоков, при условии, что как минимум одно из этих обращений является записью.
Под синхронизацией здесь подразумеваются такие инструменты, как низкоуровневые атомарные операции. Фактически, утверждение о предотвращении всех гонок данных — это такой способ сказать, что вы не сможете случайно «поделиться состоянием» между потоками. Любое обращение к данным, включающее их изменение, должно обязательно проводиться с использованием какой-нибудь формы синхронизации.
Гонки данных — это только один (хоть очень важный) пример состояния гонки, но, предотвращая их, Rust помогает избежать других, скрытых форм гонок. Например, бывает важно обеспечить атомарность обновления одновременно нескольких участков памяти: другие потоки «увидят» либо все обновления сразу, либо ни одно из них. В Rust наличие ссылки типа &mut на все соответствующие области памяти в одно и то же время гарантирует атомарность их изменений, потому что ни один другой поток не сможет получить к ним доступ на чтение.
Стоит остановиться на секунду, чтобы осмыслить эту гарантию в контексте всего множества языков программирования. Многие языки предоставляют безопасность работы с памятью с помощью сборщика мусора, но сборка мусора не помогает предотвращать гонки данных.
Вместо этого Rust использует владение данными и заимствования для реализации своих двух ключевых положений:
Будущее
Когда Rust только создавался, каналы были встроены в язык, и в целом подход к многопоточности был довольно категоричным.
И это очень здорово, потому что это значит, что способы работы с потоками в Rust могут всё время развиваться, предоставляя новые парадигмы и помогая в отлове новых классов ошибок. Такие библиотеки, как syncbox и simple_parallel, — это только первые шаги, и мы собираемся уделить особое внимание этой области в несколько следующих месяцев. Оставайтесь с нами!