aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design')
-rw-r--r--docs/design/009-layout.md2
-rw-r--r--docs/design/010-surfaces.md62
2 files changed, 63 insertions, 1 deletions
diff --git a/docs/design/009-layout.md b/docs/design/009-layout.md
index d985003..de5563d 100644
--- a/docs/design/009-layout.md
+++ b/docs/design/009-layout.md
@@ -29,7 +29,7 @@
|---|---|---|
| `core/` | 실행·소유권·한도·완료(`runtime.c`, `flow.c`), 앱 모델(`module*.c`: 실행 전에 검증한 모듈, 트랜잭션인 이벤트, draft인 상태), 코어가 저장소에 요구하는 것(`state.h`, `store.h`) | `build/libpure.a`(저장소와 함께) |
| `store/` | 트리 저장소. 한 파일, 커밋은 한 연속 쓰기와 동기화 한 번 | `libpure.a` 안 |
-| `serve/` | `pure-serve`: HTTP 호스트, 나가는 전달, 제자리 JSON. OpenSSL을 쓰는 부분(https 수신자로의 TLS, 봉인한 전달)은 따로 두어 쓰는 서버만 매핑한다 | `build/pure-serve`, `build/pure-crypto.so` |
+| `serve/` | `pure-serve`: HTTP 호스트, 나가는 전달, 제자리 JSON, 노드 안에서 도는 사람을 위한 표면([010](010-surfaces.md)). OpenSSL을 쓰는 부분(https 수신자로의 TLS, 봉인한 전달)은 따로 두어 쓰는 서버만 매핑한다 | `build/pure-serve`, `build/pure-crypto.so` |
| `cli/` | 명령행 호스트 | `build/pure-run`, `build/pure-edit-module` |
| `prototypes/` | 이전 문서 런타임. `core/`와 `store/`를 가져다 빌드한다 | `make -C prototypes/connections` |
diff --git a/docs/design/010-surfaces.md b/docs/design/010-surfaces.md
new file mode 100644
index 0000000..1c5100f
--- /dev/null
+++ b/docs/design/010-surfaces.md
@@ -0,0 +1,62 @@
+# 010 — 표면: 노드 안에서 사람에게 보이는 앱
+
+2026-09-27. 메신저를 만들려면 한 기기에서 앱이 네트워크(봉인한 전달, 088)와 화면(Vega)을 함께 가져야 한다. 그 둘을 어떻게
+잇는지 정한 것.
+
+## 문제
+
+- pure-serve는 앱의 상태를 가진 소유자 스레드 하나와 연결 스레드들로 된 노드다. 보기는 GET, 입력은 POST이고, 전달선은 이
+ 노드의 출력을 다른 노드로 봉인해 보낸다. 사람을 위한 표면은 없다.
+- Vega의 표면(터미널, 픽셀, KMS 디스플레이)은 앱을 제 프로세스 안에서 돌린다(`PureApp`을 직접). 네트워크가 없다.
+- 두 프로세스가 같은 저장소를 쓰면 안 된다. 저장소는 쓰는 이가 하나다.
+
+길은 둘이었다.
+
+1. **원격 표면.** Vega가 노드의 HTTP API를 쓰는 클라이언트가 된다. 화면마다 보기를 GET으로 받고(JSON), 키와 글자를
+ POST하고, 바뀐 것은 긴 폴링으로 안다. 화면이 다른 기기에 있어도 된다. 그러나 상태와 화면 사이에 HTTP와 JSON이 들어서고,
+ Vega의 모든 읽기를 원격 판으로 한 번 더 써야 한다.
+2. **노드 안의 표면.** pure-serve가 표면 라이브러리를 매핑해 제 스레드에서 돌린다. 표면은 연결 스레드가 보기를 읽는 것과
+ 같이 마지막 저장의 스냅숏(080)을 제자리에서 읽고, 이벤트는 요청과 같은 줄로 소유자에게 준다.
+
+로드맵은 Vega가 "뷰 상태 변경 바이트를 화면과 입출력 버퍼로 직통 바인딩"하기를 바란다. 2가 그것이다. 상태의 주인이
+하나이고, 사람과 프로그램이 같은 노드를 쓰며, 새 호스트가 생기지 않는다. JVM이 JavaFX를 싣는 것과 같은 관계다. 런타임이
+라이브러리를 싣고, 런타임은 화면을 모른다(009의 선). 1이 필요해지면(다른 기기의 화면) 그때 HTTP 쪽에 더한다.
+
+## 계약
+
+`serve/surface.h`. 표면 라이브러리는 `pure_surface_run(const PureSurface *, argc, argv)`을 내보낸다. pure-serve는
+`--surface LIBRARY [-- ARGUMENTS]`로 그것을 매핑하고, 소유자를 띄운 뒤 제 스레드에서 부른다. 표가 주는 것은 이렇다.
+
+- `read(host, reader)`: 마지막 저장의 스냅숏을 붙잡아 표면의 리더 앱에 보인다. 보기, 행, 텍스트는 `core/module.h`의 함수
+ 그대로 읽는다. `release`로 돌려준다.
+- `changes`: eventfd. 보기가 바뀌는 저장(스냅숏 게시)마다, 그리고 서버가 멈출 때 읽을 수 있게 된다. 표면은 제 입력
+ 장치와 함께 poll한다. 새 저장을 기다리며 도는 일이 없다.
+- `press(host, input)`, `type(host, input, text)`: 키 입력을 눌렀다 떼고, 텍스트 입력에 글자를 넣는다. 소유자가 요청과
+ 같은 버스트에서 적용하고 저장한 뒤에 돌아온다. 다른 종류의 입력이나 없는 입력은 `PURE_INVALID`다.
+- `stopping(host)`: 멈출 때다. pure-serve는 연결 스레드가 멈춘 뒤, 소유자를 멈추기 전에 표면을 기다린다. 표면이 기다리는
+ 마지막 이벤트를 소유자가 아직 적용할 수 있다.
+
+앱 모델의 코드는 프로세스에 한 벌이다. pure-serve가 제 `pure_*` 함수를 동적 심볼로 내보내고(`--export-dynamic-symbol`),
+표면은 그것을 부른다. 표면이 제 사본을 싣지 않으므로 판이 어긋날 일이 없다. 표에는 `sizeof(PureApp)`,
+`sizeof(PureModule)`도 있어서, 다른 배치로 빌드된 표면은 스스로 거절한다.
+
+표면의 이벤트는 호스트가 없는 요청(`Job.host == NULL`)이다. 소유자의 버스트 규칙(한 라운드에 저장 한 번, 문서 키는
+버스트에 한 번), 게시, 전달선이 모두 요청과 같다. 결과는 `hand_back`이 표면의 기다리는 이에게 준다. 상태가 없는 모듈에는
+소유자가 없으므로 표면을 둘 수 없다.
+
+## 비용
+
+표면을 쓰지 않는 서버에는 게시할 때 분기 하나, `Job`과 `Applied`에 포인터 하나다. 전과 번갈아 쟀다. 64 B GET은 140.9k 대
+139.9k/s, 앱 합계 GET은 여덟 라운드 중앙값 126.3k 대 126.3k, 20행 페이지는 96.1k 대 95.3k로 같다. 내보낸 심볼 157개로
+실행 파일의 읽기 전용 부분이 11 KB, 유휴 RSS가 24 KiB 늘었다.
+
+## 검사
+
+`tests/check.py`의 `server_surface`는 검사용 표면(`tests/surface-probe.c`)을 빌드해 싣는다. 그 표면은 텍스트 세 줄을
+넣고 키를 누르며 저장마다 보기를 적는다. 검사는 이것들을 본다.
+
+- 표면의 이벤트가 HTTP의 보기에 보인다.
+- HTTP로 넣은 입력이 `changes`로 표면에 보인다.
+- 다른 종류의 입력과 없는 입력은 거절된다(8, `PURE_INVALID`).
+- 표면은 서버와 함께 0으로 끝난다.
+- 상태가 없는 모듈은 표면을 거절한다(종료 2).