Roku OS 15.0에서 고성능 데이터 오케스트레이션

발행: (2025년 12월 28일 오전 12:04 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

MoveIntoField와 MoveFromField

전통적으로 node.field = myAA와 같은 할당은 깊은 복사를 수행했습니다. Roku OS 15.0에서는 MoveIntoField()MoveFromField()가 소유권을 실제로 이전합니다. 데이터가 노드로 이동하고 로컬 변수는 비게 됩니다.

' --- BEFORE (The Blocking Copy) ---
' ...do some work
' m.top.content = hugeAA 
' 1. Task thread stops.
' 2. Render thread stops.
' 3. Both wait for a deep copy to finish. 
' 4. Breakpointing here kills the app because 
'    the Render thread is "stuck" waiting.

' --- AFTER (The Non-Blocking Move) ---
sub finishTask(hugeAA)
    ' ...do some work
    ' MoveIntoField transfers the memory pointer instantly.
    m.top.MoveIntoField("content", hugeAA)

    ' hugeAA is now empty. The Task can exit or move on without 
    ' holding a redundant copy in memory.
    ? "Task complete. hugeAA is now empty: " ; hugeAA.IsEmpty()
end sub

Render Thread References: SetRef and GetRef

때때로 데이터를 이동하거나 복사하고 싶지 않을 때가 있습니다—단지 Render 스레드가 객체의 단일 인스턴스를 바라보게 하고 싶을 때 말이죠. 이제 Render 스레드에만 제한된 Reference API를 통해 이것이 가능해졌습니다.

  • SetRef(): AA를 필드에 참조 형태로 할당합니다(복사 없음).
  • GetRef(): 해당 참조를 가져와 사용합니다.
  • CanGetRef(): 참조가 존재하는지 확인하는 안전 검사입니다.

Note: SetRef()를 사용할 때 필드 옵저버는 알림을 받지 않습니다. 이는 관측자 알림의 오버헤드를 피하고자 하는 고주파 데이터(예: 좌표 추적)에 이상적입니다.

' --- BEFORE (Memory Inefficiency) ---
' m.childA.data = m.top.content ' Copy 1
' m.childB.data = m.top.content ' Copy 2 (Memory usage doubled)

' --- AFTER (Robust Reference Sharing) ---
' This must happen on the Render Thread (e.g., in an observer or queue handler)
sub shareTaskData()
    if m.top.CanGetRef("content")
        ref = m.top.GetRef("content")

        ' Pass the pointer to children. No deep copies are made.
        m.childA.SetRef("sharedContent", ref)
        m.childB.SetRef("sharedContent", ref)
    end if
end sub

roRenderThreadQueue

roRenderThreadQueue API는 Task 스레드가 UI 스레드에 데이터를 전송할 때 Rendezvous( UI를 멈추게 하는 동기 잠금)를 발생시키지 않음으로써 “SceneGraph Hole” 문제를 해결합니다. 이를 스레드‑안전 메일함이라고 생각하면 됩니다: Task 스레드는 메시지를 넣고 바로 진행하고, Render 스레드는 프레임 사이에 메시지를 처리합니다.

핵심 API

  • roRenderThreadQueue – 매니저 노드. Render 스레드(수신)와 Task 스레드(전송) 모두에서 초기화합니다.
  • AddMessageHandler(message_id, handler_name) – 지정된 ID와 일치하는 메시지가 도착했을 때 실행할 함수를 등록합니다. 하나의 ID에 여러 핸들러를 연결할 수 있으며, 등록된 순서대로 실행됩니다.
  • PostMessage(message_id, data) – 데이터를 이동시킵니다(고성능).
  • CopyMessage(message_id, data) – 전송 전에 데이터를 깊은 복사합니다.
' --- BEFORE (Rendezvous Dependency) ---
' m.top.updateData = hugeAA 
' Scene.brs has an observer on task.updateData.
' The moment this assignment happens, the threads are "locked."

' --- AFTER (Decoupled Messaging) ---
' [Inside Task Thread]
sub runTask()
    hugeAA = loadMassiveJson()

    queue = CreateObject("roRenderThreadQueue")
    queue.PostMessage("FEED_UPDATE", hugeAA)
end sub

' [Inside Component/Scene .brs]
sub init()
    m.queue = CreateObject("roRenderThreadQueue")
    m.queue.AddMessageHandler("FEED_UPDATE", "onFeedUpdate")
end sub

sub onFeedUpdate(data as Object, info as Object)
    ' data is the moved AA. No Rendezvous ever occurred.
    m.myGrid.content = data
end sub

결론

이 아키텍처 변화는 SceneGraph 자체 이후 Roku 개발에서 가장 중요한 변화라고 할 수 있습니다. 새로운 API를 사용하면 대형 데이터 구조를 복사하는 대신 단순히 가리키게 할 수 있어 여러 실질적인 이점을 제공합니다:

  • 저사양 디바이스 구원: 제한된 RAM을 가진 디바이스(예: Roku Express)에서 깊은 복사로 인한 메모리 급증이 사라져 대용량 JSON 페이로드를 저가 하드웨어에서도 처리할 수 있습니다.
  • ‘잔크’ 종말: RowList 또는 기타 UI 컴포넌트에서 발생하던 미세한 끊김이 MoveIntoFieldPostMessage가 O(1) 연산이기 때문에 사라져 Render 스레드가 반응성을 유지합니다.

관련 링크

Back to Blog

관련 글

더 보기 »

메인 스레드는 당신 것이 아니다

Main Thread는 사용자의 리소스입니다. 사용자가 사이트나 앱을 방문하면, 브라우저는 단일 스레드를 할당해 JavaScript를 실행하고, 그들의…