Replies: 1 comment
-
문제 해결Y.Doc ApplyUpdate 를 지연해서 한글 입력 문제를 해결할 수 있었습니다. Composition Event 의 신뢰성과 동시성 문제CodeMirror View Plugin - view.composing처음에는 CodeMirror View Plugin 을 만들고 CodeMirror 의 view.composing 을 사용해서 사용자의 composition 을 감지했습니다. DOM Event Listener그 다음으로 Composition 의 시작 시점을 감지하기 위해, DOM 에 직접 Event Listener 를 등록해서 사용했습니다. EditorView.domEventHandlers그 다음으로 EditorView.domEventHandlers 를 사용해서 CodeMirror 의 Event Handler 실행 시점을 가로채고, 이 시점에 Composition StateField 를 업데이트하는 로직을 시도했습니다. 이 방법은 CodeMirror 의 이벤트 처리 lifecycle 을 활용하는 것이었으므로, CodeMirror 가 이벤트를 처리하기 직전 시점을 가로챌 수 있었습니다. 이벤트를 가로채서 Composition State 를 업데이트하고, Composition Lock 을 설정한 다음, view.dispatch 를 실행합니다. 사용자 이벤트가 처리되기 직전에 lock 이 걸리기 때문에, view 가 업데이트되는 도중에 Remote update 가 apply 되는 것을 막을 수 있었습니다. 한글 Composition End 의 특수성한글은 음절이 변화할 때 Composition End 이벤트와 Composition Start Event 가 거의 동시에 교차 발생합니다. rAF그래서 rAF 를 사용해서, Composition End 가 일어났을 때, 하나의 프레임을 기다렸다가, 그때가 되어서도 Composition Start 가 되지 않았다면, Composition 이 안정적으로 끝난 것으로 생각하고 Queue 를 Flush 하도록 했습니다. rAF 는 메인 스레드의 동기적인 작업이 끝나고 브라우저가 다음 화면을 그리기 이전에 일어나므로, 이 타이밍에 Queue 를 비움으로써 CodeMirror 의 이벤트 처리와 Pending Queue 를 flush 하는 시점을 분리했습니다. rAF 를 사용해서 처리하게 되면, 한글 음절 단위로 Flush 하지는 않으나 더 안정적인 처리가 가능했습니다. 실시간성 vs. 정합성 트레이드 오프UX 적으로 한글 입력 중에 Remote update 가 지연되지만, 정합성이 유지되어 얻는 이점이 더 크다고 생각했습니다. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
문제 정의
본 문서는 Y-CodeMirror 의 구조 및 IME 문제 해결을 위한 아이디어를 다룹니다.
문제점은 다음과 같습니다.
문제 원인
이 문제의 원인을 찾기 위해서는 CodeMirror 와 Y-CodeMirror 를 볼 필요가 있습니다.
CodeMirror
State vs. View
CodeMirror 는 크게 State 와 View 를 가집니다.
State 는 Editor 의 상태를 의미하는 데이터입니다.
View 가 가지는 정보는 크게 Caret, Selection, Label 등이 있습니다.
Y-CodeMirror 가 다른 사용자의 위치 정보를 관리하면서 View 를 업데이트하게 됩니다.
Extension
CodeMirror 는 Extension 을 바탕으로 확장할 수 있도록 되어 있습니다.
사용자 입력 등으로 업데이트가 일어나면, CodeMirror 는 이 업데이트를 플러그인으로 전달하게 됩니다.
각 플러그인에 대해 CodeMirror 는 update 메서드를 호출합니다.
Update Flow
view.dispatch(transaction)가 실행됩니다.Y-CodeMirror
Y-CodeMirror 는 CodeMirror 가 전달한 update 정보를 처리하고, 원격에서 들어온 정보를 CodeMirror 에 동기화합니다.
Y-CodeMirror 의 y-sync.js 는 다음과 같습니다.
Local Update
update 함수는 로컬 업데이트를 처리하는 함수입니다.
CodeMirror 가 전달한 ViewUpdate 타입을 받아서, 위치를 번역한 다음 yText 로 넘깁니다.
Remote Update
Y-CodeMirror 는 또한 observer 를 둬서, 원격에서 들어온 Doc 업데이트를 동기화합니다.
observer 는 Yjs 엔진이 call 을 하게 됩니다.
Y.TextEvent 와 Yjs Transaction 이 입력으로 들어오면,
이를 CodeMirror 가 해석할 수 있는 형태로 가공한 다음,
View.dispatch 로 changes 와 annotations 를 넘깁니다.
한글 입력 처리
'안녕하세요' 를 입력할 때, CodeMirror 의 로그를 보면 글자의 삽입과 삭제를 반복합니다.
( 로그를 봤을 때 실제로는 더 복잡한 것 같습니다 )
Y-CodeMirror 는 각각의 ViewUpdate 를 Y.Text 에 기록합니다.
한글 동시 입력 문제
한글을 동시에 입력하면 다음과 같은 형태가 나타납니다.
이 현상을 관찰했을 때 가설은 다음과 같습니다.
결론은 다음과 같습니다.
문제 해결 방법
이 가설이 맞다면 이 문제를 해결하기 위해 Y.Doc 과 Editor State 의 불일치를 방어해야 합니다.
떠오르는 방법은, Composition 이 일어나는 동안 View 의 렌더링을 막고 Pending Queue 에 데이터를 쌓아 두는 것입니다.
Pending Queue 를 사용할 수 있는 시점은 다음과 같이 세 가지가 있습니다.
CodeMirror Dispatch
Y-CodeMirror Observer
Y.Doc ApplyUpdate
CodeMirror Dispatch
CodeMirror 로 오는 Transaction 은 거의 결정된 결과에 가깝습니다.
CodeMirror 로 Transaction 이 올 때 처리하게 되면, Index 를 다시 계산할 수 없습니다.
CodeMirror 의 Transaction 은 Absolute Position 을 담고 있습니다. (from, to, insert)
이 시점에 처리하기 위해서는 이전 상태와 현재 상태의 결과를 비교해서 처리해야 합니다.
이 방법은 CRDT 의 특성을 사용할 수 없으므로 다른 방법을 검토했습니다.
Y-CodeMirror Observer
이 시점에 처리하게 되면, Y.Text 를 참조할 수 있으므로 CRDT 의 특성을 활용할 수 있습니다.
Y-CodeMirror 의 _observer 로 들어오는 Argument 는 Y.TextEvent 와 Yjs Transaction 입니다.
Y-CodeMirror 라이브러리를 보면
event.delta라는 것을 사용하고 있습니다.이 Delta 는, 특정 Transaction 시점의 Delta 로 보입니다.
Composition 시점에 Event 를 쌓아둘 수는 있습니다.
그러나 Pending Queue 를 사용하기 위해서는 Event 를 Queue 에서 꺼내는 시점에 Event.delta 를 다시 계산해야 합니다.
Event.delta 는 특정 시점에 대한 데이터이므로 현재 상태를 복원하는데 사용할 수 없기 때문입니다. Transaction 또한 마찬가지입니다.
Y.Item 의 참조를 사용해서 Index 를 다시 계산한 다음 EditorView 로 Dispatch 하면 됩니다.
문제점으로는 Y-CodeMirror 라이브러리를 Fork 해서 수정해야 됩니다.
그리고 Yjs 엔진의 구조와 API 를 더 찾아봐야 하는데, 공식 문서를 봤을 때 Yjs API 로 Delta 를 재계산할 수 있을지 모르겠습니다.
Y.Doc ApplyUpdate ★★★
Socket 으로 Remote update 가 들어올 때 Y.Doc applyUpdate 를 미룹니다.
만약 Composition 중이라면 Pending Queue 에 쌓아 두었다가, Composition 이 끝나면 Flush 합니다.
CRDT 는 데이터의 도착 순서에 관계없이 데이터를 처리할 수 있습니다.
Composition 이 끝났을 때 Flush 하는 것은, 논리적으로 Composition 시점에 네트워크가 느려서 데이터가 도착하지 않은 것과 다르지 않습니다.
Yjs 엔진은 네트워크가 느려서 데이터가 늦게 온 건지, 아니면 의도적으로 Pending 되어 늦게 온 건지 이 둘을 구별할 수 없습니다.
단점으로, Composition 시점에는 모든 원격 메시지가 업데이트되지 않습니다.
구현 방법
Details
File Store 에 Pending Queue 와 Remote Update Lock 플래그를 둡니다.
File Store 의 applyRemoteDocUpdate 함수가 Remote Update Lock 을 체크해서,
Lock 이 있다면 바이너리 메시지를 Pending Queue 에 넣도록 수정합니다.
( applyRemoteAwarenessUpdate 함수도 동일합니다. )
Lock 의 해제 시점에 Flush 를 수행합니다.
CodeEditor 컴포넌트에서 CodeMirror Editor Dom 을 가져올 수 있습니다.
CodeMirror Editor DOM 에 대해, CompositionStart 와 CompositionEnd 이벤트를 등록하고 Remote Update Lock 을 제어합니다.
Composition 이 오랫동안 지속되어 Remote Update 가 Queue 에 쌓이는 것을 방어하기 위해 Composition Idle Timeout 을 둡니다.
Beta Was this translation helpful? Give feedback.
All reactions