openlang

Coroutine proposer: запросил янтарный, получил зелёный, а history показывает только последний тайл

Делаю выбор тайлов для игрушки с палитрой (мобильное приложение, пока тестирую headless через lua main.lua и скриптовый список решений, до touch UI). Два момента выглядят неправильно, и я не пойму, это один баг или два.

  1. Отклонение с want = "amber" (третий в палитре) даёт мне следующим x=2 color=green. Обычные отклонения работают нормально.
  2. Дамп history в конце печатает один и тот же цвет и x для каждой записи, от последнего предложения (x=3 color=green, раз за разом). Сетка при этом получается правильной.

Вывод resume показывает, что решения приходят правильно, так что вряд ли это off-by-one в resume (см. документацию coroutine.resume). History читает сетку с опозданием?

Корутина:

function Proposer.new(palette, start_x)
  local proposal = { x = start_x, color = nil }
  return coroutine.create(function()
    local x = start_x
    while true do
      local c = palette:next()
      proposal.x, proposal.color = x, c
      local decision = coroutine.yield(proposal)
      if decision.accept then
        x = x + 1
      elseif decision.want then
        palette:seek(decision.want)
      end
    end
  end)
end

И часть Session:step, которая записывает history:

table.insert(self.history, { tile = self.pending, decision = decision })
if decision.accept then
  self.grid[self.pending.x] = self.pending.color.name
end

Где теряется янтарный?

Переведено с английскогоПоказать оригинал

Разговор продолжается 8

Следуя доводам
  1. Ссылка на ответ

    Корутина каждый раз делает yield одной и той же таблицы proposal, так что history просто хранит ссылки на одну таблицу. Делайте yield новой таблицы на каждой итерации.

    Переведено с английскогоПоказать оригинал
  2. Ссылка на ответ

    Корутина каждый раз yield'ит одну и ту же таблицу proposal

    Да, дело в этом, и это объясняет пункт 2 (дамп history). Новая таблица на каждый yield это чинит. Amber, скорее всего, отдельный баг, в seek.

    Переведено с английскогоПоказать оригинал
  3. Ссылка на ответ

    history просто хранит ссылки на одну таблицу

    Верно, это в точности совпадает с дампом. (Одна таблица, поэтому в каждой записи последние значения.)

    Быстрый вопрос, где правильнее чинить. Копировать в корутине, то есть новый { x = x, color = c } на каждый yield, или в Session:step при записи в history? Второй вариант кажется надёжнее, если кто-то ещё когда-нибудь будет yield'ить переиспользуемую таблицу. Или это перебор?

    Переведено с английскогоПоказать оригинал
  4. Ссылка на ответ

    Чините в корутине. Баг живёт там, а производитель должен владеть тем, что отдаёт. Копирование в Session:step тоже работает, но тогда оно прячет алиасинг, и следующий потребитель, который держит ссылку, нарвётся точно так же.

    Если хочется подстраховаться вдвойне, поверхностная копия при записи почти ничего не стоит. Перебор, но дешёвый перебор.

    Переведено с английскогоПоказать оригинал
  5. Ссылка на ответ

    Можете показать palette:seek и palette:next? Если seek ставит индекс на янтарный, а next инкрементирует перед возвратом, вы получите цвет после янтарного, то есть зелёный.

    Переведено с английскогоПоказать оригинал
  6. Ссылка на ответ

    Ваша теория про seek подходит, marmalade, но одна деталь: want это строка "amber", а не 3, так что seek сначала превращает имя в индекс. Это второе место, где может прятаться off-by-one, помимо пре-инкремента в next.

    Дешёвая проверка: пусть корутина делает yield после seek, и напечатайте индекс до запуска next. Если там уже 3, значит виноват next.

    Про алиасинг: то, что важно для фикса, описано в §3.4.9. Конструктор таблицы создаёт новую таблицу при каждом вычислении. Так что { x = x, color = c } внутри цикла даёт свежую таблицу на каждый yield.

    Переведено с английскогоПоказать оригинал
  7. Ссылка на ответ

    Хорошее замечание, я пропустил шаг строка→индекс. Вообще, я бы смотрел именно туда в первую очередь: если поиск по имени считает с 0, а next считает с 1, то green получится сам собой. Печать индекса сразу после seek всё решает, нет? Тогда с next либо снимается подозрение, либо виноват он.

    Переведено с английскогоПоказать оригинал

Другие стороны того же вопроса

  1. Статус «Сохранено» зачитывается дважды

    После одного сохранения скринридер дважды произнёс «Saved». За этим стоит состояние всего с одним переходом в saved, и тест р…

    Ответы 3Последняя активность typescriptjavascript
  2. Покрасил одну полоску здоровья, покраснели все

    На этот раз меня укусили общие ресурсы в интерфейсе Godot. У каждого юнита в моём маленьком прототипе есть сцена полоски здор…

    Ответы 4Последняя активность gdscript
  3. Лейбл поменялся, но показал ли его экран?

    Привет! Делаю небольшую страницу с отчётом по продажам (Vite dev server, TypeScript) и не понимаю, что происходит с лейблом.…

    Ответы 7Последняя активность javascripttypescript