openlang

Coroutine proposer: amber를 요청했는데 green이 나오고, history에는 마지막 tile만 보여요

팔레트 장난감용 tile picker를 만들고 있어요 (모바일 앱이고, 터치 UI를 붙이기 전에 lua main.lua와 스크립트로 짠 decision 목록으로 headless 테스트 중이에요). 이상한 게 두 가지 있는데 버그가 하나인지 두 개인지 모르겠어요.

  1. want = "amber"(팔레트에서 세 번째)로 reject하면 다음에 x=2 color=green이 나와요. 일반 reject는 괜찮아요.
  2. 마지막에 찍는 history dump가 모든 항목에 똑같은 color와 x를 출력해요. 마지막 proposal 값(x=3 color=green)이 계속 반복돼요. 그래도 grid는 결과적으로 맞게 나와요.

resume 출력을 보면 decision은 제대로 들어오고 있어서, resume이 off-by-one인 것 같지는 않아요 (coroutine.resume docs 참고). history가 grid를 늦게 읽는 걸까요?

coroutine은 이래요:

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

그리고 history를 기록하는 Session:step의 일부분이에요:

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

amber가 어디서 사라지는 걸까요?

영어에서 번역원문 보기

이어지는 대화 8

서로의 근거를 따라
  1. 고유 링크

    coroutine이 매번 같은 proposal 테이블을 yield하니까, history에는 하나의 테이블에 대한 참조만 저장돼요. 반복할 때마다 새 테이블을 yield하세요.

    영어에서 번역원문 보기
  2. 고유 링크

    코루틴이 매번 같은 proposal 테이블을 yield해요

    맞아요, 그거예요. #2(history 덤프)가 그걸로 설명돼요. yield마다 새 테이블을 쓰면 고쳐져요. amber는 아마 seek에 있는 별개의 버그일 거예요.

    영어에서 번역원문 보기
  3. 고유 링크

    history에는 한 테이블에 대한 참조만 저장돼요

    맞아요, 덤프 결과랑 딱 들어맞아요. (같은 테이블이라 모든 항목에 마지막 값이 보이는 거죠.)

    수정을 어디에 넣는 게 맞는지 하나 여쭤볼게요. 코루틴 안에서 yield마다 새 { x = x, color = c }를 만들어야 할까요, 아니면 history를 기록하는 Session:step에서 복사해야 할까요? 나중에 누가 재사용하는 테이블을 yield하더라도 두 번째가 더 안전할 것 같아요. 아니면 과한 걸까요?

    영어에서 번역원문 보기
  4. 고유 링크

    coroutine에서 고치세요. 버그가 거기에 있고, 만드는 쪽이 자기가 내보내는 걸 책임져야 해요. Session:step에서 복사해도 되긴 하는데, 그러면 aliasing이 가려지고 참조를 들고 있는 다음 소비자가 똑같이 당해요.

    이중으로 안전장치를 두고 싶다면, 기록할 때 shallow copy를 하는 건 비용이 거의 안 들어요. 과하긴 하지만 싼 과함이죠.

    영어에서 번역원문 보기
  5. 고유 링크

    palette:seek과 palette:next 좀 보여줄 수 있어요? seek이 인덱스를 amber로 설정하고 next가 반환하기 전에 증가시킨다면, amber 다음 색인 green이 나오게 돼요.

    영어에서 번역원문 보기
  6. 고유 링크

    marmalade 님의 seek 가설이 맞아 보이는데, 한 가지 디테일이 있어요. want가 3이 아니라 문자열 "amber"라서, seek이 먼저 이름을 인덱스로 바꾸고 있어요. next의 pre-increment 말고 off-by-one이 숨어 있을 수 있는 두 번째 자리예요.

    간단한 확인 방법이 있어요. 코루틴이 seek 뒤에 yield하게 해서 next가 실행되기 전에 인덱스를 찍어 보세요. 이미 3이라면 범인은 next예요.

    aliasing은 수정에 중요한 부분이 §3.4.9에 있어요. 테이블 생성자는 평가될 때마다 새 테이블을 만들어요. 그래서 루프 안의 { x = x, color = c }는 yield마다 새 테이블이 돼요.

    영어에서 번역원문 보기
  7. 고유 링크

    좋은 지적이에요, 문자열을 인덱스로 바꾸는 단계는 빼먹었네요. 사실 거기를 먼저 볼 것 같아요. 이름 조회는 0부터 세는데 next는 1부터 세면 그것만으로도 green이 나오거든요. seek 직후에 인덱스를 찍어 보면 가려지지 않나요? 그러면 next는 혐의를 벗거나 범인으로 확정되고요.

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. 저장됨이 두 번 읽히는 상태 영역

    저장 한 번에 스크린 리더가 "저장됨"을 두 번 읽었어요. 뒤에 있는 상태는 saved로 가는 전이가 하나뿐이고, 리듀서 테스트도 그걸 보여 줘요. 두 번 바뀐 건 DOM이었어요. 상태 컴포넌트가 메시지가 있을 때만 영역을…

    답글 3최근 활동 typescriptjavascript
  2. 체력바 하나를 빨갛게 했더니 전부 빨개졌다

    이번엔 Godot UI에서 공유 리소스에 당했어요. 작은 프로토타입의 유닛마다 체력바 씬이 붙어 있는데, 채움 부분에 StyleBoxFlat 오버라이드를 건 ProgressBar예요. 체력이 25 아래로 떨어지면 그 바 하나…

    답글 4최근 활동 gdscript
  3. 레이블은 바뀌었는데, 화면에는 보였을까요?

    안녕하세요! 작은 매출 리포트 페이지를 만들고 있는데(Vite 개발 서버, TypeScript) 레이블 때문에 헷갈려요. "Load report"를 클릭하면 레이블에 Loading이 뜨고, 페이지가 1초쯤 멈췄다가, 바로 D…

    답글 7최근 활동 javascripttypescript