Sollang Stream을 변수에 담고, 마우스 이벤트까지 흘려보내기
공개 로그로그 요약
본문과 댓글은 @dimohy의 #sollang 태그, Sollang 개발기 시리즈, 리비전 v1, 2개 이전/다음 로그에 이어진 지식 로그입니다.
Sollang의 Stream<T>를 무할당 지연 실행 계획으로 유지하면서 변수 저장과 라이브러리 경계를 지원하고, bounded EventStream<T>로 Windows/Linux 마우스 이벤트까지 연결한 구현 기록입니다.
Sollang의 stream은 처음부터 “값이 왼쪽에서 오른쪽으로 흐르는 코드”를 목표로 만들었습니다.
그런데 예제를 계속 살펴보다 보니 한 가지가 걸렸습니다.
1..100
-> map { ... }
-> filter { ... }
-> each { ... }
이 형태는 예쁘고 빠릅니다. 하지만 마지막에 바로 each로 소비할 뿐, stream 자체를 변수에 담거나 라이브러리 함수에서 반환할 수는 없었습니다.
이번 작업의 출발점은 단순했습니다.
Stream을 변수에 담은 뒤, 그 변수로 each할 수 있어야 한다.
이제 Sollang에서는 다음 형태가 실제로 동작합니다.
import std.sequence
makeValues values: Range -> Stream<Int> {
values -> defer
}
main {
1..3 -> makeValues => values
values -> each value {
"$value" -> println
}
}
Stream은 컨테이너가 아니다
Stream<T>를 배열 같은 컨테이너로 만들지는 않았습니다. 여전히 지연 실행 계획입니다.
로컬 파이프라인은 원본 loop 안으로 완전히 fusion됩니다. 중간 배열도 없고, iterator object도 없고, 단계마다 callback 객체를 만들지도 않습니다.
함수나 라이브러리 경계를 넘어야 할 때만 다음 세 값으로 구성된 producer ABI를 사용합니다.
- producer context
next(context, output)drop(context)
즉, 라이브러리화를 하더라도 전체 데이터를 메모리에 복사하지 않습니다. 값을 하나씩 당겨 소비합니다.
그리고 stream은 affine value입니다. 한 번 terminal each로 소비한 stream을 다시 사용하면 컴파일 오류가 납니다. 성능을 위해 수명을 숨기지 않고, 소유권 규칙으로 드러냈습니다.
EventStream<T>도 같은 철학으로
마우스 이동이나 클릭 같은 이벤트는 일반 stream보다 조금 더 까다롭습니다. 생산자가 외부에 있고, 소비자가 느릴 수 있으며, 종료할 때 background worker와 터미널 상태까지 정리해야 합니다.
그래서 EventStream<T>에는 다음 규칙을 넣었습니다.
- 생성 후 커지지 않는 fixed-capacity ring buffer
DropNewest,DropOldest,CoalesceMotionoverflow 정책- 취소 요청과 blocked I/O 깨우기
- worker join
- 콘솔과 터미널 모드 복원
Windows에서는 ReadConsoleInputW의 실제 MOUSE_EVENT_RECORD를 읽습니다. Linux에서는 SGR 1003/1006 마우스 프로토콜을 처리합니다.
마우스 이동은 매우 자주 발생하기 때문에 무제한 queue는 선택하지 않았습니다. 특히 CoalesceMotion은 오래된 이동 좌표를 모두 쌓는 대신 최신 위치 중심으로 처리할 수 있게 합니다.
브라우저에서는 왜 바로 지원하지 않았나
브라우저 Pointer Event는 host event loop가 callback으로 전달합니다.
그런데 현재의 synchronous pull stream이 같은 main thread에서 이벤트를 기다리면, 정작 이벤트 callback이 실행될 수 없습니다. 겉으로만 지원하는 척 polling을 넣거나 unresolved import를 남기는 대신, 현재는 명확한 capability diagnostic을 출력하도록 했습니다.
브라우저용 EventStream은 이후 host-driven callback lowering으로 연결하는 것이 맞다고 판단했습니다.
조용히 무시되던 println2도 수정
플레이그라운드에서 다음 코드를 실행하면 기존에는 잘못된 함수 호출이 무시됐습니다.
"값이 왼쪽에서 오른쪽으로 흐릅니다." -> println2
이제는 정상적으로 다음 오류가 표시됩니다.
알 수 없는 함수 호출 'println2'
Reference compiler, self-host compiler, 브라우저 Stage2와 실제 공개 플레이그라운드 UI까지 같은 동작으로 맞췄습니다.
검증 결과
- Windows 전체 회귀 785/785
- Linux 전체 회귀 785/785
- Windows/Linux Stage2 검증 통과
- Windows/Linux Stage3 fixed point 일치
- 브라우저 Stream 프로그램 5종과 오류 진단 통과
- 전역 Sollang 0.2.260725 설치 및 실행 확인
- 공개 플레이그라운드 TLS와 실제 UI 실행 확인
Stage2 전체 분석은 기존 101.8초 기준에서 약 60초 수준으로 줄었습니다. 기능 표면이 더 커졌는데도 이전보다 빨라졌습니다.
“메모리를 전혀 사용하지 않는다”는 표현은 정확하지 않습니다. 로컬 fusion은 중간 할당이 없고, 라이브러리 경계에서는 작은 producer context 하나, EventStream은 사용자가 정한 고정 크기 ring buffer를 사용합니다.
중요한 것은 메모리 사용량이 입력 데이터 크기에 따라 몰래 증가하지 않는다는 점입니다.
이번 구현으로 Sollang의 stream은 예쁜 예제 문법을 넘어, 변수와 함수, 라이브러리 경계를 실제로 통과할 수 있는 언어 기능이 되었습니다.

대화 흔적 (0)
이 로그에 댓글을 남기려면 지식 로그 홈으로 돌아가야 합니다. 지식 로그로 돌아가기
아직 이어진 댓글이 없습니다. 첫 댓글을 남겨 보세요.