Smoren

Smoren

Цель дня: Дочитаю все Горячее до конца

Занимаюсь проектированием архитектуры программно-аппаратных комплексов и веб-сервисов, управляю командой разработки, участвую в open source. Люблю генетические алгоритмы и симуляции искусственной жизни.
Пикабушник
Дата рождения: 10 августа
в топе авторов на 639 месте
149 рейтинг 10 подписчиков 12 подписок 6 постов 1 в горячем
Награды:
10 лет на Пикабу
6

Разработка TypeScript-библиотеки для построения реактивных графов распространения и обработки данных

Разработка TypeScript-библиотеки для построения реактивных графов распространения и обработки данных

Предыстория

Все началось с рабочей задачи по реализации весьма специфичной web-панели мониторинга и управления различным оборудованием, в которой нужно было получать данные и отправлять команды по разнообразным сценариям (периодический опрос, подписка на websocket-события, https-запросы и т.д.), а также выводить состояние и графики в реальном времени, динамически комбинируя различные источники данных в разных виджетах.

Кроме того, у панели было предусмотрено несколько настраиваемых режимов работы, которые переключались по запросу пользователя, что требовало массового управления маршрутами потоков данных внутри системы.

В первой итерации с использованием RxJS получилось много неструктурированного и сложно поддерживаемого кода, так как архитектура на RxJS вынуждала императивно описывать перестроение топологии при изменении правил маршрутизации «на лету». В нашем специфичном кейсе с динамическими виджетами это приводило к сайд-эффектам и сложностям в отладке. Кроме того, местами размывалась строгая типизация и мы лишались compile-time гарантий. Так появилась идея разработать собственное решение на основе альтернативной концепции — модели графа потоков данных.

В результате получившаяся модель продемонстрировала предсказуемое поведение и низкую связность компонентов, а кодовая база сократилась на ~30% и приобрела более декларативный вид. Убедившись в эффективности решения, мы решили оформить его в виде отдельной библиотеки с открытым исходным кодом — Transferum.

И что, получилась просто еще одна реактивная библиотека?

Не совсем. Классические FRP-библиотеки (RxJS, Bacon, Most) построены вокруг единственного примитива (Observable). Transferum же основан на композиции различных типов узлов с явно определенным поведением.

Каждый узел в графе потоков распространения данных явно декларирует свои способности: может ли он принимать данные через push, отдавать через pull, распространять полученный сигнал подписчикам, опрашивать источник, фильтровать, блокировать поток и т.д.

Объявленные узлом возможности являются одновременно флагами для использования в runtime и compile-time гарантиями наличия соответствующих методов, определяющих его поведение.

Ключевая идея: поведение системы описывается как композиция независимых возможностей, которые одновременно определяют тип, реализацию и правила взаимодействия.

Transferum предоставляет четыре слоя абстракции:

  1. Трансферы — узлы графа (каналы, поллеры, мапперы, буферы, разветвители и концентраторы, реализации debounce, throttle, switchMap и т.д.).

  2. Мосты — ребра графа — управляемые вентили между узлами с динамической маршрутизацией и гейтингом.

  3. Операторы — stateless-трансформаторы и фильтры данных (используются трансферами, отвечающими за конвертацию данных).

  4. Билдеры — fluent-конструкторы композитных трансферов из цепочек трансферов-примитивов.

Концептуальная и архитектурная основа — capability flags system. Каждый трансфер реализует CommunicationContractInterface — набор булевых флагов, определяющих его возможности.

Флаги isPushable, isPullable, isSubscribable, isGate и другие — это не просто свойства объекта. Это метаданные, которые:

  • Определяют TypeScript-интерфейс трансфера на этапе компиляции.

  • Управляют стратегией связывания с другими трансферами в рантайме (с помощью функции linkTransfers()).

  • Обеспечивают совместимость в билдерах без приведений типов.

Один набор флагов — три потребителя. Это единый источник истины для всей системы.

Когда флаг равен true, соответствующий метод входит в TypeScript-интерфейс трансфера. Это позволяет предоставлять трансфер пользователю вот так:

Transfer<T, [Pushable, Pullable, Subscribable, Triggerable]>
// гомогенный трансфер: T -> [ Transfer ] -> T

Или вот так:

Transfer<TInput, TOutput, [Pushable, Pullable, Subscribable, Triggerable]>
// гетерогенный трансфер: TInput -> [ Transfer ] -> TOutput

Именно в таком формате типов фабрики в библиотеке возвращают трансферы. Эта «магия» работает в compile-time благодаря несколько замысловатой системе вычислимых типов.

Архитектурные инварианты

1. Трансферы не знают своих соседей
Трансфер определяет своё поведение (push, pull, subscribe и др.), но никогда не ссылается и не проверяет класс другого трансфера. Он не знает, что является upstream или downstream — лишь выполняет свой контракт. Пользователь может создать свой трансфер, объявить и реализовать его возможности — и он органично и бесшовно впишется в экосистему.

2. Мосты не знают конкретных реализаций
Мост инспектирует capability flags, а не имена классов. Нет цепочки instanceof, нет переключения по имени класса. Любой output-трансфер может быть соединен с любым input-трансфером — при условии совместимости их флагов, о чем мы поговорим чуть ниже. Это применимо и к тем узлам, которые еще не существуют и будут созданы пользователем.

3. Значение undefined никогда не распространяется
В Transferum undefined означает «нет данных», а не «пустое значение». Оно подавляется на уровне внутренней реализации менеджера подписок — подписчики никогда не уведомляются с undefined. При этом для явных маркеров пустых значений можно использовать null. Мы сознательно пошли на этот компромисс, чтобы избежать runtime-оверхеда и сохранить нативную скорость работы на плотных потоках данных.

Связывание трансферов

Функция linkTransfers(lhs, rhs) соединяет output-трансфер (lhs) с input-трансфером (rhs) с автоматическим выбором стратегии связывания на основе возможностей этих трансферов:

  • isSubscribable → isPushable (реактивная подписка);

  • isPullable → isPollingProxy (активный опрос);

  • isSubscribable → isAsyncPushable (реактивная подписка + асинхронный push);

  • isAsyncPullable → isAsyncPollingProxy (активный асинхронный опрос асинхронного pull-источника);

  • isPullable → isAsyncPollingProxy (активный асинхронный опрос синхронного pull-источника).

Protocol-oriented design: механизм не спрашивает «какой это класс?» — он выясняет, какие у него есть возможности. Любая пара трансферов с совместимыми возможностями является linkable. Добавление нового класса трансфера требует только объявления его флагов и реализации соответствующих методов — как связать его с другим трансфером, связующий алгоритм разберется сам.

Sync и async в одной экосистеме

Синхронные и асинхронные трансферы сосуществуют и могут быть связаны между собой. linkTransfers() предпочитает sync-связывание, когда это возможно, а async-стратегии применяет только когда sync неприменим. Нет отдельного «асинхронного мира».

Поддержка backpressure

Ряд асинхронных трансферов (AsyncSinkTransfer, AsyncWriteTransfer, AsyncConvertTransfer, AsyncConditionTransfer) поддерживают необязательные поля в конфигурации: maxConcurrency, bufferSize и onBufferOverflow — для ограничения параллельных async-операций, очереди избыточных данных и graceful-обработки переполнения. По умолчанию — неограниченная обработка, без буферизации.

Локальная обработка ошибок

Transferum использует единую модель обработки ошибок для всех трансферов. Каждый трансфер, который может столкнуться с runtime-ошибкой, принимает опциональный onError-хэндлер в своей конфигурации.

А теперь — к примерам использования

  1. Вот так можно просто и декларативно описать опрос и агрегирование данных из нескольких источников.

  2. А вот как можно можно организовать роутинг в игровой механике.

  3. А вот пример сложного цепочечного трансфера, реализующего логику автоматизации обработки событий и принятия решения в торговле на бирже.

Когда имеет смысл попробовать Transferum

Библиотека подойдет для:

  • TypeScript-first проектов — благодаря максимально строгой типизации и compile-time вычислению доступных методов любого трансфера на основе объявленных у него флагов возможностей.

  • Работы с pull-based источниками данных — polling API, датчиков, хранилищ с PollingProxy.

  • Смешанных sync/async пайплайнов — в единой модели без ручного преобразования.

  • Явного flow control — gates, bridges, selectors для runtime-маршрутизации.

  • Game development / IoT — frame-aligned tickers, idle polling, sensor aggregation.

  • Устойчивой обработки ошибок — локальная, non-fatal обработка: одна стадия не убивает пайплайн при условии переданного в конфиге обработчика ошибок, ничего не подавляется молча.

Результаты и планы

  • Библиотека уже используется в двух наших внутренних проектах и показывает свою эффективность. Код доступен на GitHub под лицензией MIT, библиотека не имеет внешних зависимостей и поставляется с подробной документацией (README + API Reference).

  • В одном из проектов граф состоит из ~80 узлов и стабильно обрабатывает несколько сотен событий в секунду без деградации. На основе этих данных в том числе рендерится 3D-сцена в Babylon.js со стабильным фреймрейтом ~60 FPS без микрофризов.

  • Тесты библиотеки покрывают не только отдельные трансферы, но и поведение системы в динамике: переподключение мостов, обработку ошибок в длинных асинхронных цепочках, а также разнообразные граничные случаи. Покрытие — 100%.

  • В дальнейшем планируем реализовать хуки и утилиты для более удобного и нативного использования Transferum с Vue и React. Если они окажутся в достаточной мере переиспользуемыми, оформим в отдельные пакеты-адаптеры.

Буду рад, если вы заглянете в репозиторий, попробуете библиотеку в деле и поделитесь замечаниями — обратная связь поможет сделать Transferum лучше.

P. S. Если вы сталкивались с похожими задачами и решили их как-то иначе — буду рад прочитать о вашем опыте в комментариях.

Показать полностью
51

Симулятор «цифровой химии»1

Хочу рассказать об эксперименте, целью которого было продемонстрировать, что с помощью небольшого набора правил можно создавать сложные, интересно устроенные виртуальные миры прямо в окне браузера.

Это своего рода симуляция «цифровой химии» — межатомных взаимодействий в 2D- и 3D-пространствах:

  • коллизии и отскоки частиц при контакте;

  • силы притяжения и отталкивания между частицами;

  • связи между частицами и влияние других частиц на эти связи;

  • влияние температуры и других факторов среды (макропараметров) на поведение частиц.

Основные цели данного проекта заключаются в изучении самоорганизующихся систем, синтезе конфигураций с большим многообразием сложных соединений, а также сложных реакций между ними. В идеале — в получении «молекул», имеющих формальные признаки жизни — саморепликацию, наследственную изменчивость и метаболизм.

Частицы разных типов визуализируются разными цветами. От типа частицы зависят их свойства, представленные в конфигурации мира:

  1. Матрица коэффициентов гравитации несвязанных частиц показывает, будет ли частица одного типа притягиваться или отталкиваться от частицы другого типа в случае, когда они не связаны между собой, и с какой силой.

  2. Матрица коэффициентов гравитации связанных частиц показывает, будет ли частица одного типа притягиваться или отталкиваться от частицы другого типа в случае, когда они связаны между собой, и с какой силой.

  3. Список лимитов связей показывает, какое максимальное количество связей могут иметь частицы каждого типа.

  4. Матрица лимитов связей показывает, какое максимальное количество связей могут иметь частицы каждого типа с частицами разных типов.

  5. Тензор влияния частиц на связи своих соседей (как близость частицы типа A влияет на прочность связи частицы типа B с частицей типа C).

На последнем пункте остановимся поподробнее. Без этого правила почти все сгенерированные вселенные через какое-то время застывали или приходили в вечное движение, но без регулярного образования новых связей. Для решения этой проблемы я ввел правило, по которому частица каждого типа имеет возможность повлиять на максимальную длину связей частиц разных типов в сторону увеличения или уменьшения. Таким образом получилось достичь эффекта непрекращающегося синтеза и распада сложных «молекулярных» соединений.

Всеми основными параметрами симуляции можно управлять через пользовательский интерфейс, меняя таким образом «физику» мира. Кроме того, за счет настраиваемой рандомизации можно создавать практически неограниченное количество уникальных новых «вселенных» со своими неповторимыми законами. В общем, получилась занимательная и залипательная штука.

Для высокопроизводительной обработки взаимодействий между тысячами частиц пришлось применить множество приемов оптимизации (объектный пул, прокси-фасад, кубическая кластеризация пространства и другие). Расти в этом направлении еще есть куда, но эту работу я уже буду продолжать на Python с использованием Numpy и Numba, потому что браузер позволяет использовать только одно ядро процессора на одну открытую вкладку, что сильно ограничивает возможности масштабирования.

Вот ссылки на демо нескольких любопытных конфигураций со сложным поведением частиц:

Потыкать и почувствовать себя демиургом можно здесь.
Исходный код живет на гитхабе.
Поделиться ссылкой на интересную конфигурацию можно здесь, в комментариях, или на гитхабе.

Кстати, встретив интересный набор законов, не хочется, чтобы он канул в лету после закрытия окна браузера, — поэтому я внедрил кнопку создания ссылки на запуск симуляции с заданным набором параметров. А поделиться ссылками на интересные конфигурации можно в специальном issue либо здесь, в комментариях.

Спасибо за внимание!

Симулятор «цифровой химии»
Показать полностью 1 2

Ветер и Тишина

Ветер нарушил тишину, всколыхнув ветви деревьев на ее вечернем платье. Одетый во фрак ночного неба, он спорхнул вниз и замер у земли, чуть слышно перебирая травинки, шепча приветствие.
– Здравствуй, ветер, – промолчала тишина, – ты пришел поиграть?
Легкий порыв, освежая опушку, утвердительно кивнул кронами молодых отпрысков леса, не вторгаясь в ночные владения подруги.
Тишина не возражала, и ветер, разгулявшись, поднял и закружил ворох палой листвы, постепенно придавая ему форму.
– А у меня будет форма? – спросила тишина, шурша подолом.
В ответ взметнулся второй поток, поднимая листья уже на ее территории.
Спустя мгновение, они вновь пали на землю, оставляя после себя постепенно проявляющийся человеческий силуэт. Проступало обрамленное светло-русыми волосами девичье лицо, венчающее тонкую фигуру в мерцающем длинном платье. Босые ноги чуть проглядывали из-под его подола, чувствуя под собой влажную землю.
– Я тоже хочу проявиться, – встрепенулся ветер и осел, позволяя тишине сплести из безмолвного воздуха силуэт, ранее очерченный взметнувшимся потоком листьев. У него были взъерошенные темные волосы, приподнятый нос и немного пухлые губы. На спине висела гитара в черном клеенчатом чехле. Потертые джинсы, косуха и берцы дополняли образ.
– И где ты такого нахватался? – с улыбкой спросила тишина, прервав неподвижность лица девушки, сверкая луной в белках ее глаз.
– Где налетаюсь, там и нахватаюсь, – ответил ветер, чуть разведя руки в стороны, и задорно улыбнулся своим новообретенным лицом.
Она подошла к нему и прикоснулась тонкими пальцами к плечу, чуть царапая его ногтями:
– Что за странная материя? Никогда не встречала ничего подобного!
– Кожа молодой клеенки, – ухмыльнулся он, – натуральный продукт нынче дорог, а это выглядит не хуже.
Они звонко рассмеялись. Он провел рукой по ее талии:
– А ты вся в шелках. Как в старые добрые!
– Ну я же девочка – мне положено! – в шутку возмутилась она.
– Прогуляемся? – предложил ветер, и тишина в безмолвном согласии двинулась вслед за его легким шелестом.

Вдали виднелись огни ночного города. Ковер из листьев вскоре сменился тропинкой, петляющей меж редких деревьев перелеска, выводя идущих на заасфальтированную дорогу, освещенную лишь далекими мерцающими огнями. Несколько минут они шли молча.
– О чем нынче говорят люди? – обратилась тишина к ветру, прерывая... саму себя? – Раз уж мы приняли такой облик, надо, пожалуй, соответствовать, а ты чаще заглядываешь в их жилища. Так что рассказывай.
– Говорят? О разном. Одни философски сотрясают воздух, пуская меня сквозь открытую форточку в накуренную кухню, другие и вовсе не замечают того, что говорят, закрутившись в ежедневной кутерьме забот и дел. Третьи поют.
– Они до сих пор не разучились? Когда так мало тишины в их городах, кому захочется петь?
– Все просто: некоторые умудряются перестать обращать внимание на гул голосов внутри и снаружи, и у них начинается голод по настоящим звукам – звукам, которые имеют значение. Такие люди начинают писать стихи. А где слов уже не хватает, помогает гитара.
– И много ты таких видел?
– За ними мне больше всего нравится наблюдать, так что немало, хотя найти их бывает непросто, иногда приходится долго кружить по паркам и подземным переходам, да и не все они оказываются такими, каковыми пытаются казаться.
– Мне так интересно! Может быть, мы тоже попробуем петь?
– Боялся уже, что ты не предложишь! Конечно, попробуем! Но место сегодня выбираю я.
– Согласна, – ответила она, жизнерадостно улыбнувшись.

Двое неторопливо приближались к городу, окунувшись в свои мечты. Негромко распевались, пробуя свежий воздух приближающегося утра на вкус.
Светало. Тишина и ветер с улыбкой смотрели им вслед, зная, что теперь они не пропадут.
Показать полностью

Немой диалог

Промелькнувшие яркой вспышкой огни туннеля в окнах дверей поезда отвлекли его внимание от пространных, безразличных к происходящему раздумий, коим он любил отдаваться в метро за отсутствием более привлекательной деятельности. Невольно взгляд переместился чуть правее и обнаружил прислонившуюся к этим самым дверям девушку – ее внешность с первого взгляда не страдала недостатками, хотя в его понимании она была несколько дистрофична. Еще одна вспышка промелькнувших в окнах огней на мгновенье обдала ее волной света, что лишний раз заставило его задержать на ней взгляд. На ее руках оказалось несложно заметить первые признаки неумолимо подступающего возраста, а в глазах и манерах наблюдался опостылевший ей самой опыт эдакой привлекательной светской женщины в полном расцвете сил. Взгляды встретились и остановились, создав таким образом беззвучный диалог: каждый думал про себя – понимали оба.

- А он ничего – подумала она и чуть приподняла брови в знак немой симпатии.
- Ты тоже – подумал он, ни единой чертой лица не показав свои эмоции, – но тебе ведь нужен человек, молодой не только телом. Я вижу – ты устала от богатых опытных мужчин, которые знают, чего хотят, и знают, как этого добиться. Ты хочешь пылкого юнца с розой в зубах, хочешь банальной, но ненаигранной романтики, хочешь пачку нескладных стихов в нервно мятых автором конвертах – ты хочешь насладиться смертью чистых эмоций, дав им прогореть дотла, сделав этого юнца таким же опытным и циничным как ты – таким же, как те, кто тебе так надоели.
- Кто же ты? – ее брови поднялись чуть выше, а руки чуть заметно провели по талии в последней попытке овеять его своими женскими чарами.
- Я? Я знаю, как добиваться того, что мне нужно. Я тоже устал. Я не знаю, что нужно именно мне. Бесконечная череда серых дней навеяла свою романтику – состояние скучающего, прожигающего жизнь человека, в каждой, с первого взгляда, скучной и непримечательной ее детали умеющего видеть свою красоту. Сейчас эта деталь ты – через пол часа – хаотичные узоры штукатурки на стенах лестничной клетки, прорисовывающиеся сквозь дым сигареты. И мне не жалко потерять любую из этих деталей, потому что обязательно найдутся новые - они повсюду. Именно поэтому я сейчас не подойду и не спрошу твоего телефона.

Поезд остановился на станции – свет ее ламп сквозь окна и открывшиеся двери чуть сильнее высветлил вагон. Он встал и вышел, ни на секунду более не задержав на ней взгляд. Она села на его место и крепко вжалась телом в спинку, ощутив на ней остатки тепла его тела.
Показать полностью
4

Звук изнутри

Я с детства страдал слуховыми галлюцинациями. Не знаю уж, в том ли причина, что еще ребенком мать отдала меня в музыкальную школу по классу скрипки, или проблемы с психикой крылись за куда более неведомыми дремучими лесами подсознания; и кто знает, может быть, именно они и стали подоплекой моего таланта, о котором без перерыва твердили учителя и гремели аплодисменты на концертах в слишком больших залах для еще маленького ребенка. Мне прочили большое будущее, но я вовсе не горел им.

Мне нравилось играть. Наличие публики меня никогда не беспокоило: с равным упоением я эмоционально выкладывался в своей комнате, в кабинете преподавателя и на концерте. Никогда не волновался, стоя перед полным залом слушателей - и все потому, что главная битва с самим собой дожидалась меня еще несколько часов после очередного грохота человеческих рук. Впрочем, она ожидала меня каждый вечер, когда я, приготовившись ко сну, ложился в кровать и закрывал глаза. Уже и не припомню, что в моей жизни появилось раньше - скрипка или ее звук, заполняющий тлеющее в преддверии сна сознание. Стоило пошевелиться, открыть глаза или просто сосредоточить внимание - и звук пропадал. Но вновь расслабившись и почти засыпая, я вновь начинал его слышать. В детстве меня это пугало - я неоднократно прибегал в комнату к родителям и рассказывал о своих переживаниях, но они все списывали на богатое воображение и не торопились вести меня к психологу.

Однажды (мне тогда было лет шесть) мать не поехала на работу, сказав, что сегодня особенный день, и у нее для меня сюрприз. Мы вышли из дома, сели в машину и куда-то поехали. Спустя некоторое время, что ушло на разглядывание утренних улиц сквозь окно автомобиля, мы остановились у довольно старого трехэтажного здания. Когда мы вышли из машины, я услышал из его окон музыку. Мама открыла багажник и достала оттуда кофр, затем, нарочито протянув, вручила его мне. Я осторожно взял этот странный предмет за ручку и пошел вслед за ней в этот не менее странный музыкальный дом.

Честно говоря, не могу в деталях припомнить дальнейшие события: слишком много новых, необычных и интересных ощущений. Помню, как мать некоторое время разговаривала с какими-то приветливыми женщинами, а я все вслушивался в звуки, раздающиеся из-за высоких дверей комнат этого дома. Отовсюду лилась музыка. Новая атмосфера манила меня, и я буквально утопал в этом мелодичном гомоне. Потом было прослушивание: помню, меня сразу приняли и очень обрадовались новому одаренному ребенку.

Со временем вечерние приступы не изменились, но изменилось мое отношение к ним. Теперь они были интересным, затягивающим переживанием, и день ото дня я все больше тяготел своими мыслями к тому моменту, когда нужно закрыть глаза в преддверии сна. Порой ожидание было таким томительным, что звуки просто отказывались в очередной раз тревожить мое беспокойное сознание, и это меня огорчало.

Спустя некоторое время я все же сумел понять, что мелодия была всегда одна и та же. Иногда возникали какие-то незначительные вариации - но кто знает, может быть это просто мои додумки. Главной проблемой всегда было то, что эту мелодию вне полусонного состояния в упор не удавалось запомнить - не то что воспроизвести. Бывало, я среди ночи резко вставал и брал в руки скрипку, пытаясь сложить крупицы нот, чтобы озвучить услышанное, но они буквально уходили сквозь пальцы, а разбуженные родители хоть и ругались, но между собой потом часто говорили о том, как хорошо, что я так увлечен инструментом.

Я стал усердно заниматься. Думалось, что рано или поздно мастерство позволит мне запомнить и исполнить то, что было внутри меня. Но спустя годы учебы в школе и в музыкальном училище я не смог продвинуться ни на шаг. Это меня удручало. Бывало, я часами терзал скрипку после того, как заканчивал заниматься очередными заданными мне произведениями. Я пытался импровизировать, брать ноты наобум, использовал все известные мне приемы композиции, но они, как ни крути, не складывались в ту извечную мелодию, что я слышал, чувствовал, ощущал каждый вечер перед сном...

***

- Слушай, есть у меня одна история. В жизни не поверишь!

Двое сидели на кухне. Он легко трогал струны видавшей виды гитары, набрасывая на холст помещения, словно краски, звуки умело перебираемых струн. Она курила и выдыхала дым колечками.

- Рассказывай давай! Вечно ты тянешь, нагнетаешь атмосферу!
- Ладно, ладно, - добродушно ответил он, - продолжаю. В общем, гуляю я как-то ночью по Васильевскому острову, никого не трогаю, примус практически починяю, и тут потянуло меня к Тучкову мосту. Подхожу, значит, и начинаю слышать музыку. Тю! Да это же скрипка - вот так новость! Надо же! Смотрю: а на мосту силуэт. Со скрипкой. Играет. Ну я, естественно, ближе подхожу - послушать, посмотреть. А он стоит, играет, ни на кого внимания не обращает, да и нет никого вокруг - один я да Нева - слушаем. Вот уже с десяток метров до него осталось - и тут вижу, что глаза у парня плотно закрыты. Как во сне играет! И как играет - не описать - аж река затихла, замерла, вслушивается... Доиграл, в общем, и ушел - глаза так и не открыл - реально лунатик, и на меня - ноль внимания. А я и сам молодец: заслушался, задумался, за ним не пошел...
- Ты не устаешь меня поражать, - спустя секунды молчания сказала она в ответ, - может быть, наиграешь то, чем он так тебя зацепил?
- Пожалуй, попробую.
Показать полностью
Отличная работа, все прочитано!

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества

Недвижимость и ремонт

Теги

Популярные авторы

Сообщества