Приветствую вас, читатели.

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

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

Будет несколько статей, это первая.
Для тех, кто хочет весь список сразу, держите:

Об авторе

Позвольте представиться: я Антон, программист-даос и любитель китайского чая.

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

Начинал с классического Java Backend, затем добавил Groovy и Kotlin, а потом пришло время моего первого фреймворка (о нём — позже).
После JVM-мира я погрузился в Эльбрус и увлекательное портирование ПО на разных языках.

Но сегодня хочу рассказать о самом интересном (хотя и не самом свежем) опыте — работе с Dart.
Сейчас я Platform Engineer с уклоном в Go, но Dart приключения заслуживают отдельной истории.

А зачем

В технических статьях редко встретишь объяснение, зачем они вообще написаны. Мне же хочется быть честным с читателем с первых строк.

Дело в том, что за 10 лет программирования у меня не было периода, когда бы я не писал код после работы или учёбы. Накопилось немало интересного материала — того, что действительно достойно публикации.

Но каждый раз, задаваясь вопросом «Зачем?», я не находил убедительного ответа.

Сейчас он появился: это попытка достучаться.

  • Достучаться до тех, кто верит, что OpenSource автоматически означает массовую доработку кода. Я в это не верю.

  • Достучаться до тех, кто не верит, что может изменить что-то важное — даже сам язык. А вот в это, в возможность изменить все, я верю крепко.

  • Достучаться до уставших. Любой бы устал: текущее состояние гибкости и поддерживаемости кода во Вселенной оставляет желать лучшего.

  • Достучаться до Dart-сообщества: ребята, пора вытаскивать Dart из фронтенд-ниши. Взгляните, на что он способен! Небольшое усилие — и он завоюет бэкенд.

  • Достучаться до команды Dart (вдруг кто-то прочитает):

Обожаю эту картинку — регулярно напоминаю себе её смысл.

И ещё — повод. Один мой друг сказал: «Статья — это тоже коммуникация».

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

О чем пойдет разговор

Итак, начинается цикл из трех статей о моем эксперименте с Dart Language и Dart VM.

Предыстория такова: обычный разработчик захотел добавить в язык сложную фичу.

Давайте представим: вам нравится язык, но чего-то в нем не хватает. Не магическую кнопку "сделай за меня", а что-то технически значимое — будь то новая библиотека, синтаксический сахар, ключевое слово или даже механизм исполнения кода.

В моем случае это были корутины в Dart. И мне удалось реализовать их — с кучей ограничений, но все же.

Наверняка подумали: "Ну вот, еще одна история успеха про самодельный велосипед". Но суть не в этом. Я хочу рассказать не столько о том, что и как сделано, а о том, что я чувствовал на разных этапах — как разработчик и просто человек.

Будет много технических деталей, конечно, но фокус — на состояниях, через которые проходил.

Прежде чем начать, спросите себя: "А стоит ли мне это читать?" Помогу с ответом.

Кому может быть интересно:

  • Тем, кто хочет добавить фичу в язык

  • Тем, кто видит Dart не только как Flutter-рантайм

  • Любителям разбираться в компиляторах и внутренностях языков

  • Выгорающим разработчикам (может, отвлечетесь?)

  • Студентам в поисках темы для диплома

Кому, возможно, не зайдет:

  • Тем, кто не интересуется устройством языков

  • Flutter-разработчикам, которым не нужны глубины Dart (про Flutter — ни слова)

Теперь, когда все формальности соблюдены, налью пуэр и, если позволите, начну.

В начале было слово

И имя ему — ART. Так я окрестил свой первый фреймворк для Java/Kotlin.

Что умел этот зверь:

  • Элегантная обработка HTTP/RSocket трафика

  • Работа с Tarantool

  • Интеграция (ранее была) с реляционными БД

  • Взаимодействие (было) с Kafka

  • Динамическое применение изменений конфигураций на лету

А еще — компиляция в нативный бинарник через GraalVM.

Я задумал его как инструмент для чистой и гибкой разработки сервисов на Java/Kotlin. Без рефлексии, без модификаций байт-кода, без лишней инверсии контроля, без аннотаций.

Простота без потери мощности — чтобы разработчик мог легко создавать production-решения. Да, очередной велосипед, ничего сверхъестественного.

Но потом я понял: сложности есть не только в бэкенде. Фронтенд и инфраструктура тоже страдают.

Так появились art-platform и art-react — ещё два велосипеда в коллекцию.

Идея была проста: единый инструментарий для всего цикла разработки — от написания кода до деплоя.

Почему корутины ?

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

Среди них были Tarantool, Golang, Koishi, node-fibers и ucontext — все они так или иначе связаны с элегантной абстракцией под названием корутины.

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

Если хотите погрузиться в тему, вот хорошие отправные точки:

Это одна из тех концепций, которые меняют взгляд на асинхронный код.

Почему Dart ?

На определённом этапе разработки ART я чётко сформулировал технические требования к языку для реализации своих идей — и Java, увы, перестала им соответствовать.

Именно тогда я случайно открыл для себя Dart — и обнаружил, что он идеально ложится в мои критерии. Так закончилась моя Java-эпоха и начался Dart-путь.

Почему именно Dart? Мне нужен был язык, который:

  • Работает везде: нативные десктопы (Linux/Windows/macOS), мобильные платформы (iOS/Android), веб и бэкенд

  • Обладает выразительным синтаксисом для создания простых, но эффективных языковых конструкций

  • Одновременно компилируется в компактный бинарник без зависимостей и позволяет горячую перезагрузку кода при разработке

  • Даёт низкоуровневый доступ к нативным технологиям (C/C++/Rust) для преодоления ограничений рантайма

  • Обеспечивает автоматическое управление памятью (в любой форме)

Рассматривал альтернативы: Go, Rust, Kotlin, Nim, Crystal, V, C/C++. Но выбор пал на Dart.

История мучительного выбора с таблицами сравнения и метаниями достойна отдельного поста — но здесь просто примем как факт: Вселенная подкинула мне Dart, и я не стал спорить.

Новый путь

Прежде чем погружаться в очередной фреймворк, я решил расширить экосистему Dart, создав несколько ключевых модулей:

После этого относительно успешного опыта велосипедостроения я подошёл к моменту, где началась настоящая магия — история "романтических" отношений между Dart и корутинами.

Мне отчаянно понадобились корутины. И я решил их реализовать.

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

О том, что остаётся за кадром большинства технических статей.

Начальное состояние

Осознав творческий потенциал Dart, я испытывал настоящий восторг. Этот язык открывал потрясающие возможности:

  • Кроссплатформенный UI через Flutter

  • Нативная совместимость через FFI

  • Векторизация вычислений через SIMD

  • Графика через шейдеры

  • Метапрограммирование через кодогенерацию

Я был полон амбиций и готовности погрузиться в совершенно новую для меня область — реализацию корутин.

Состояние было противоречивым: с одной стороны — вдохновение от безграничных возможностей, с другой — страх перед внедрением новой модели исполнения кода. Но творческий азарт перевесил все сомнения.

Поговорим про понятия

Решимость на максимуме, пуэр заварен, IDE запущена — пора разбираться в основах.

Но сначала вопрос: что не так с родными async/await и Future в Dart? Ведь они и так дают асинхронность.

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

  1. Создают цепочки Future — каждый await это точка разрыва потока исполнения

  2. Требуют явной пометки async — заранее "заражают" всю цепочку вызовов

  3. Работают через механизм событийной очереди — что иногда даёт неочевидные накладные расходы

  4. Не имеют состояния между вызовами — каждый await это полноценный вход/выход из контекста

Именно эти ограничения и заставляют задуматься о более гибкой абстракции — корутинах, которые:

  • Сохраняют состояние между приостановками

  • Позволяют тоньше управлять переключениями контекста

  • Могут работать без привязки к событийному циклу

Но чтобы понять, как это сделать в Dart — сначала нужно разобрать, как устроены существующие механизмы асинхронности.

Итак, с чем мы работаем ?

  • Thread

  • Isolate

  • Future

  • Coroutine & Fiber

Я постараюсь дать мои собственные определения этим понятиям с учетом работы с Dart.

Thread

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

Однако в Dart всё иначе. Вернее, сама возможность работы с потоками есть, но скрыта от разработчика. Под капотом Dart VM отлично справляется с многопоточностью.

Классическое понимание потока — механизм для параллельного выполнения кода.

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

  • стек вызовов

  • хранилище

  • атрибуты

  • состояние

Конкретная реализация может добавлять и другие компоненты.

Управление этой структурой ложится на runtime — будь то виртуальная машина языка или непосредственно операционная система.

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

Например, если нужно задействовать все доступные ядра процессора, потоки позволяют явно указать планировщику runtime: "этот код должен выполняться параллельно".

Isolate

Довольно свежая концепция, с которой я впервые столкнулся в Dart.

Если по-простому, изолят — это:

  • контейнер с исполняемым кодом

  • выделенная область памяти (куча)

  • набор атрибутов

Всё это "упаковывается" и отправляется в поток для выполнения.

Зачем это нужно?

У традиционных потоков есть известная проблема — состояние гонки. Когда несколько потоков работают с общими данными, возможны конфликты. Изоляты решают это радикально: у каждого изолята своя память, и они не могут напрямую влиять друг на друга.

Дополнительный бонус:

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

Связь с потоками:

Когда Dart выполняет код, он:

  1. Берет код в изоляте

  2. Назначает для его выполнения поток

Отличное объяснение этой архитектуры можно найти у @mraleph:

Кстати, если ты это читаешь, загляни сюда :)

Future

В Dart функции можно разделить на два основных типа (хотя технически их четыре, но остановимся на ключевых) — синхронные и асинхронные.

Синхронные функции

Работают предсказуемо и линейно: вызвали — получили результат. Никакой магии:

void main() {
  print("before start");
  final result = child(0);
  print("child result: $result");
  print("after start");
}

int child(int argument) {
  print("child: entry($argument)");
  return 0;
}

Вывод будет строго последовательным:

before start
child: entry(0)
child result: 0
after start

Асинхронные функции

Здесь начинается интересное. Возьмем аналогичный пример, но с async/await:

Future<void> main() async {
  print("before start");
  final result = child(0);
  print("child result: $result");
  final resultWhenReady = await result;
  print("child result when ready: $resultWhenReady");
  print("after start");
}

Future<int> child(int argument) async {
  await otherChild();
  print("child: entry($argument)");
  return 0;
}

Future<void> otherChild() async {
  print("other child");
}

Вывод выглядит неожиданно:

before start
other child
child result: Instance of 'Future<int>'
child: entry(0)
child result when ready: 0
after start

Что происходит?

  1. main вызывает child(), но не ждет его завершения

  2. Внутри child() есть await otherChild() — выполнение приостанавливается

  3. Управление возвращается в main, который продолжает работу

  4. Только после завершения otherChild() выполняется оставшаяся часть child()

Ключевые моменты:

  • Асинхронные функции не блокируют выполнение кода

  • Результат возвращается немедленно в виде объекта Future — обещания будущего значения

  • Фактическое значение можно получить через await

Важное ограничение Dart:

  • В рамках одного изолята нет настоящего параллелизма — в каждый момент времени выполняется только одна функция (синхронная или асинхронная)

  • Асинхронность ≠ многопоточность, это кооперативная многозадачность в рамках одного потока выполнения

Coroutine & Fiber

Давайте разберём этот магический трюк по косточкам. Перед нами — живая демонстрация работы корутин в Dart с использованием Fiber.

Исходное состояние:

var commonState = "";  // Наша разделяемая "память"
void main() {
  print("before start");
  Fiber.launch(mainEntry);
  print("after start");
}

Последовательность выполнения:

  1. Запускаем main() → выводится "before start"

  2. Fiber.launch(mainEntry) — создаём корутину

  3. Выводится "after start" (главный поток продолжает работу)

Внутри корутины:

void mainEntry() {
  print("main: entry");          // Шаг 1 корутины
  commonState += "main -> ";     // Изменяем состояние
  Fiber.spawn(childEntry);       // Создаём ДРУГУЮ корутину
  commonState += "main -> ";     // Продолжаем работу
  print("main: after child transfer");
  Fiber.reschedule();            // Волшебная точка переключения!
  print(commonState);            // Финальный вывод
}

Вторая корутина:

void childEntry() {
  print("child: entry");         // Шаг 1 второй корутины
  commonState += "child -> ";    // Меняем то же состояние
  Fiber.reschedule();            // Возвращаем управление
  commonState += "child";        // Продолжаем после возврата
  print("child: after main transfer");
}

Вывод

before start
main: entry
child: entry
main: after child transfer
child: after main transfer
main -> child -> main -> child
after start

Магия переключений:

  1. Первый Fiber.reschedule() в mainEntry:

    • Сохраняет текущее состояние выполнения (включая позицию в коде!)

    • Передаёт управление childEntry

  2. Fiber.reschedule() в childEntry:

    • Возвращает выполнение ТОЧНО на место предыдущей остановки

    • В mainEntry это место после первого reschedule()

Почему это круто:

  • Мы явно управляем переключениями между задачами

  • Каждая корутина сохраняет свой контекст выполнения (стек, переменные)

  • Нет привязки к потокам ОС — это "зелёные потоки" (green threads)

  • Можно реализовать кооперативную многозадачность без настоящих потоков

Fiber vs корутины:

  • Корутина — низкоуровневая концепция сохранения состояния выполнения

  • Fiber — удобная обёртка, которая даёт:

    • API для запуска (launch, spawn)

    • Контроль переключений (reschedule)

    • Управление стеком и контекстом

Это и есть тот самый "контроль над выполнением", который делает асинхронный код более предсказуемым.

Заглядываем под капот: Future

На текущий момент реализация async в Dart (как мне видится) — в основном заслуга одного человека — Александра Маркова.
Александр, если читаешь — добавь корутины, ну позязя :)

С чего начать разбор...

Функционал достаточно обширный, поэтому сосредоточимся на конкретном примере:

Future<void> main async {
  final myFuture = myFunction();
  await myFuture;
}

Future<String> myFunction() async { return "Hello, World!"; }

Как выполняется этот код с учетом async-await:

  1. Вызов main()

  2. Вызов myFunction()

  3. Неявное создание объекта Future

  4. Завершение myFunction()

  5. Ожидание myFuture и получение значения

Первое, что привлекает внимание — класс Future. С него и начнём.

Future — это удобный публичный класс, ориентированный на разработчика.
Методы хорошо документированы, их назначение понятно из названий.
Основная идея — предоставить инструменты для работы с асинхронными операциями:

  • цепочки преобразований через then

  • колбэки завершения через whenComplete

  • обработка ошибок через catchError

Но сегодня нас больше интересует не сам класс, а конструкция await myFuture.

Когда-нибудь задумывались, что скрывается за await?

Покажу:

@pragma("vm:entry-point", "call")
@pragma("vm:invisible")
Object? _await(Object? object) {
  if (_thenCallback == null) {
    _createAsyncCallbacks();
  }
  if (object is _Future) {
    if (object._isComplete) {
      _awaitCompletedFuture(object);
    } else {
      object._thenAwait<dynamic>(
          unsafeCast<dynamic Function(dynamic)>(_thenCallback),
          unsafeCast<dynamic Function(Object, StackTrace)>(_errorCallback));
    }
  } else if (object is! Future) {
    _awaitNotFuture(object);
  } else {
    _awaitUserDefinedFuture(object);
  }
  return _functionData;
}

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

Сейчас разберёмся с _SuspendState и его связью с Future. Нас интересуют два фрагмента:

object._thenAwait<dynamic>(
    unsafeCast<dynamic Function(dynamic)>(_thenCallback),
    unsafeCast<dynamic Function(Object, StackTrace)>(_errorCallback));

и

_awaitCompletedFuture(object);

_thenAwait — метод _Future, добавляющий новый _FutureListener. Каждый такой listener — это callback, вызываемый при завершении Future.
В случае _SuspendState вызовется _thenCallback, который запустит:

thenCallback(value) {
  suspendState._resume(value, null, null);
}

_awaitCompletedFuture создаёт микротаску для вызова thenCallback.

О микротасках кратко: когда код Dart не выполняется, управление переходит к VM (C++ часть).
В Dart VM есть несколько очередей событий. Микротаски попадают в начало очереди и выполняются при первой возможности.

Переходим к самой интересной части — реализации async-await в Dart VM.
Лучшее описание — в технической документации.
Я выделю ключевые моменты для общего понимания.

Заранее упомянем важный механизм Dart VM — заглушки (stubs).
Это ассемблерный код, генерируемый C++ частью VM. Простейший пример:

void StubCodeCompiler::GenerateInitStaticFieldStub() {
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for result.
  __ PushRegister(InitStaticFieldABI::kFieldReg);
  __ CallRuntime(kInitStaticFieldRuntimeEntry, /*argument_count=*/1);
  __ Drop(1);
  __ PopRegister(InitStaticFieldABI::kResultReg);
  __ LeaveStubFrame();
  __ Ret();
}

Разберём по шагам:

  1. EnterStubFrame() — сохраняет текущий фрейм (RBP) и перемещает указатель стека (RSP) в RBP

  2. PushObject(NullObject()) — подготовка регистра для результата

  3. PushRegister(InitStaticFieldABI::kFieldReg) — передача регистра с полем для инициализации

  4. CallRuntime(kInitStaticFieldRuntimeEntry, 1) — вызов C++ функции из Dart VM

  5. Drop(1) — корректировка указателя стека

  6. PopRegister(InitStaticFieldABI::kResultReg) — получение результата

  7. LeaveStubFrame() — восстановление фрейма

  8. Ret() — возврат из функции

Это простейшая заглушка. Сложные выглядят куда интереснее.

В реализации async-await используются три основные заглушки:

  1. GenerateAwaitStub (она же SuspendStub)

  2. GenerateResumeStub

  3. GenerateReturnAsyncStub

AwaitStub генерируется при вызове await и отвечает за:

  1. Создание _SuspendState (при необходимости)

  2. Сохранение текущего фрейма

  3. Вызов асинхронной функции

  4. Удаление текущего фрейма

  5. Возврат в вызывающий код (в нашем случае — в C++ часть VM, вызвавшую main())

ResumeStub реализует _SuspendState._resume:

  1. Загрузка фрейма приостановленной функции

  2. Восстановление стека

  3. Переход к точке после await

ReturnAsyncStub в нашем примере просто вызывает _SuspendState._returnAsync.

Итоговый путь выполнения:

  1. Создание _SuspendState

  2. Сохранение фрейма

  3. Вызов функции

  4. Завершение Future через _returnAsync

  5. Выполнение микротаски с thenCallback и _resume

  6. Возврат с восстановлением контекста

Заглядываем под капот: Coroutine

Пока оставлю в тайне детали реализации своих корутин - вместо этого разберём их на примере отличной базы данных Tarantool. Мои корутины заслуживают отдельного глубокого разбора, который я планирую дать в будущих статьях.

В Tarantool ключевой концепцией является Fiber - это абстракция над корутиной. С Fiber можно работать как с обычными корутинами: приостанавливать, возобновлять, делать yield для переключения на другой готовый к выполнению Fiber. За управление Fiber отвечает специальный планировщик - Fiber Scheduler.

Давайте сосредоточимся на двух фундаментальных аспектах реализации корутин в Tarantool:

  1. Как происходит переключение контекста?

  2. Что происходит, когда ни один Fiber не готов к выполнению?

Вот как выглядит реализация переключения контекста для Linux x64 в Tarantool:

void coro_transfer (coro_context *prev, coro_context *next) {
  pushq %rbp
  pushq %rbx
  pushq %r12
  pushq %r13
  pushq %r14
  pushq %r15
  movq %rsp, (%rdi)
  movq (%rsi), %rsp
  popq %r15
  popq %r14
  popq %r13
  popq %r12
  popq %rbx
  popq %rbp
  popq %rcx
  jmpq *%rcx
}

Кратко о происходящем:

  1. На вход получаем контексты текущей и следующей корутин

  2. Сохраняем callee-saved регистры на стек

  3. Сохраняем текущий стек в контекст старой корутины

  4. Загружаем стек новой корутины

  5. Восстанавливаем регистры из стека

  6. Переходим по адресу следующей инструкции (через регистр %rcx)

Функция coro_transfer - это наш "переключатель" между корутинами. Все операции с Fiber в Tarantool, требующие смены контекста, используют именно её.

Второй вопрос решается через комбинацию coro_transfer и Event Loop. Подробнее о реализации event loop можно почитать здесь, а в Tarantool используется библиотека libev.

Разберём алгоритм функции sleep:

  1. Регистрируем таймер через libev

  2. Привязываем таймер к функции пробуждения Fiber

  3. Делаем yield (переключаем контекст на вызывающий Fiber)

  4. Если планировщику нечего выполнять, он обращается к libev

  5. При срабатывании таймера libev уведомляет планировщик о готовности Fiber

Почти все операции с Fiber в Tarantool построены на связке libev и libcoro.

Ещё один интересный пример - кроссплатформенная реализация корутин с поддержкой Эльбруса. Она доступна здесь.

Именно на эти примеры я и ориентировался в своей работе. По крайней мере, так было задумано.

Хотя на практике получилось не совсем так :)

И зачем это все ?

Серьёзно, в чём смысл? Реализация async в Dart выглядит вполне солидно.

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

Но дело не в скорости. Главное — гибкость и функциональность.

Вот какие требования у меня сформировались к системе асинхронного управления:

  • Не хочу плодить лишние сущности вроде Completer просто для фиксации точек suspend/resume

  • Нужна прозрачная интеграция с FFI: чтобы события из нативного кода мгновенно пробуждали корутины в Dart без лишних накладных расходов

  • Меня не устраивает объём вспомогательного кода внутри Future — кажется, можно сделать лаконичнее

  • Разделение на sync и async функции выглядит искусственным, а переходы между ними — неудобными (см. эту проблему)

  • Ну и… шило в одном месте — просто захотелось реализовать это самому :)

Немного про Dartino

Прежде чем изобретать велосипед, я решил проверить — может, решение уже существует? И действительно, кое-что нашлось.

Знакомьтесь: Dartino — заброшенная реализация Dart VM для embedded-устройств (официальное закрытие проекта).

Но самое интересное — вот что я в ней обнаружил:

void InterpreterGeneratorX64::DoCoroutineChange() {
  LoadLiteralNull(RAX);

  LoadLocal(RBX, 0);  // Load argument.
  LoadLocal(RSI, 1);  // Load coroutine.

  StoreLocal(RAX, 0);
  StoreLocal(RAX, 1);

  Label resume;
  SaveState(&resume);
  LoadProcess(RDI);
  // RSI already loaded with coroutine.
  __ call("HandleCoroutineChange");
  RestoreState();

  __ Bind(&resume);
  __ Bind("", "InterpreterCoroutineEntry");

  StoreLocal(RBX, 1);
  Drop(1);

  Dispatch(kCoroutineChangeLength);
}

Да, это настоящие корутины в Dart.

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

Дело даже не в том, что я не смогу использовать Dartino как основу для своей работы (ведь он несовместим с текущей Dart VM).

Главная грусть в том, что 10 лет назад уже существовала компактная, эффективная реализация Dart — и её просто выбросили.

Обидно осознавать, что столько труда осталось невостребованным, без шанса на развитие.

Что с мотивацией ?

Как и обещал, фиксирую свое состояние на этом этапе разработки.

Я был расстроен из-за Dartino, но моя уверенность и мой настрой не потерялись.

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

Кстати, команде Tarantool огромное спасибо за OpenSource! Это правда нужно миру. Пожалуйста, развивайте его.

После моего небольшого анализа и поисков примеров для реализации, мой уровень решимости - 90%, потому что Dartino правда расстроил.

Нет, серьезно, скачайте себе репозиторий, посмотрите на код, почитайте, может быть среди вас найдется тот, кто сможет возродить этот проект.

Пошла жара

Следующая статья будет максимально технической. Я собираюсь рассказать о том, что из себя представляет реализация корутин на Dart.

Какие аспекты Dart VM пришлось затронуть. Какие особенности Dart мне мешали, а какие помогали на моем пути.

Разумеется, будет фиксация моего состояния к моменту создания первого рабочего прототипа.

Приглашаю посмотреть на жесткие техно-извращения с Dart VM. Тыкайте сюда.

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

Комментарии (2)


  1. pnmv
    28.05.2025 20:19

    И зачем это все ?

    Это очень нужный раздел, в данном цикле статей. Жаль, тема не раскрыта.

    Но, в принципе... не так давно, я читал правила хабра. Если не причёсывать тексты, всё становится очень похоже на местные "это вам не...".


  1. OpenA
    28.05.2025 20:19

    кроссплатформенная реализация корутин с поддержкой Эльбруса.

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

    Но, если я правильно понял предысторию, никакой корутинной библиотеки изначально небыло, была просто реализация ucоntext для разных архитектур в коде игры TaiseyProject. И именно из за того что контекст свитч на эльбрусе устроен иначе, грамотным выходом из ситуации было написание высокоуровневого api, что портировавший и сделал. Ну и ее в итоге приняли в менлайн