aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design/008-module-format.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design/008-module-format.md')
-rw-r--r--docs/design/008-module-format.md37
1 files changed, 34 insertions, 3 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index cc1a1a1..1e27976 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -1,6 +1,7 @@
# Pure 모듈 형식 v5
-2026-09-25. 구현 `prototypes/connections/module.{h,c}`, `module-text.c`, `module-list.c`, 트리 저장소 연결 `module-store.c`.
+2026-09-25. 구현 `prototypes/connections/module.{h,c}`, `module-text.c`, `module-list.c`, 상태 저장 `module-save.c`,
+트리 저장소 연결 `module-store.c`.
Pure가 실행하는 앱 정의의 형식이다. JVM이 class 파일 형식을 소유하듯 Pure가 이 형식과
검증 규칙을 소유한다. 생산자는 [aup](/srv/git/aup.git) 같은 툴체인이다. Pure는 소스·문법을 모르며,
@@ -124,7 +125,7 @@ v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마�
## 실행
-`PureApp`은 모듈 참조, 상태 값, text 상태 draft, 리스트, 열린 이벤트 하나를 가진다(1,016 B, `PureModule`은 상수
+`PureApp`은 모듈 참조, 상태 값, text 상태 draft, 리스트, 열린 이벤트 하나를 가진다(1,808 B, `PureModule`은 상수
블롭을 포함해 28,600 B). text 상태는 저장소 파일이 없는 draft라 1 MiB 텍스트도 조각 트리로 편집한다. 커서는 바이트
위치이며 항상 글자 경계에 있다. 넣기는 그 위치 이후의 커서를 뒤로(삽입 위치의 커서 포함) 밀고, 지우기는 범위
안의 커서를 범위 처음으로 모은다. 모든 이벤트에서 text draft를 잡아 두고, 실패하면 이벤트 전 버전으로 돌린다.
@@ -157,6 +158,36 @@ text·리스트가 남을 때만 위치를 유지한다.
데이터 호스트는 `PureData` 인터페이스이며, `pure_tree_data`가 트리 저장소의 draft로 구현한다.
`pure-edit-module`은 기존 문서 CLI(`pure-edit`)와 같은 명령·출력·종료 코드를 모듈로 제공한다.
+## 상태 영속
+
+데이터 호스트가 상태를 맡으면(`PureData`의 `state`·`save`, 트리 저장소에서는 `pure_tree_data`) 앱의 모든 상태가
+저장소에 산다. 앱에 저장 코드가 없다. 선언한 상태가 곧 영속 상태다.
+
+| 상태 | 저장 형태 (런타임 이름공간, 문서 키와 겹치지 않음) |
+|---|---|
+| text | `text-이름`: UTF-8 바이트 그대로. 앱의 draft가 곧 저장 문서라 입력 한 글자는 바뀐 익스텐트만 추가한다 |
+| 리스트 | `list-이름`: `PLS1`, 레코드 필드 수, 필드마다 종류·이름, 그다음 항목(수는 binary64, text는 u32 길이와 바이트). 리스트 연산이 메모리의 항목과 draft를 함께 splice한다 |
+| 수·커서·인덱스 | `values`: `PVS1`, 상태마다 종류·이름·binary64. 바뀐 이벤트에서만 다시 쓴다 |
+
+- **시작.** 저장된 상태를 이름과 종류로 불러온다. 없는 상태는 초기값으로 시작한다. 레코드가 달라진 리스트는
+ 이름·종류가 같은 필드를 옮기고 새 필드는 기본값을 쓴 뒤 다시 저장한다(교체와 같은 규칙, 마이그레이션 스크립트 없음).
+ 커서가 text 범위나 글자 경계를 벗어나거나, 인덱스가 리스트 밖이면 0이 된다. 깨진 리스트·text(형식 오류, UTF-8
+ 아님)는 시작을 `store_error`로 멈춘다. 조용히 버리지 않는다.
+- **이벤트가 곧 커밋이다.** 키·text·메시지 이벤트가 성공하면, 바뀐 상태 문서들과 메시지의 문서를 저장소의
+ **한 커밋**(`pure_draft_commit_all`)으로 공개한다. 모두 보이거나 하나도 보이지 않는다. 커밋이 실패하면 이벤트가
+ 실패하고 메모리의 상태도 되돌린다. 결과 불명(`uncertain`)이나 다른 기록자와의 충돌 뒤에는 앱이 저장소와 어긋나
+ 이후 이벤트가 충돌로 실패한다. 다시 시작하면 저장소에 있는 상태로 이어진다.
+- **frame은 늦게 쓴다.** 초당 60번 오는 frame 이벤트는 메모리에서 원자적이고, 바뀐 것은 다음 이벤트의 커밋이나
+ `pure_app_save`에 함께 들어간다. `pure_app_stop`만 하면 저장하지 않은 frame 변경은 사라진다.
+- **교체.** 모듈을 바꾸면 새 상태는 저장된 값이 있으면 그것으로 시작하고, 버린 상태의 저장본은 그대로 남는다.
+ 새 모듈의 상태는 다음 이벤트나 저장에서 공개된다.
+
+검증(`test-module`): 같은 무작위 이벤트열을 메모리 앱과 수시로 재시작하는 저장 앱에 주어 매 단계 상태가 비트 단위로
+같다(리스트 앱·text 앱 각 400단계, 재시작 20회 이상). 수·text·리스트를 한꺼번에 바꾸는 이벤트 중 커밋의 모든 단계에서
+프로세스를 죽여도 다시 시작하면 전부 이전 아니면 전부 이후다. 메시지의 문서와 상태가 함께 커밋되고 함께 실패한다.
+레코드 변경은 재시작과 교체 모두에서 항목을 옮긴다. 비용(Pi 5, SD의 ext4): 저장하는 이벤트 하나에 약 10.6 ms가
+더 든다. 대부분 두 번의 `fsync`다. 300항목 리스트에서 인덱스 이동 한 번은 파일을 약 220 B 키운다(압축 전).
+
아직 런타임 코어(`runtime.c`)의 작업·예산 모델과 연결하지 않았다. 호스트가 직접 호출한다. 실행기 코드는
-`module.c` 31.1 KB, `module-text.c` 3.6 KB, `module-list.c` 3.2 KB(`-O2` text)로 v1(7.4 KB)보다 크다. 줄이는 것이
+`module.c` 33.1 KB, `module-text.c` 3.6 KB, `module-list.c` 5.7 KB, `module-save.c` 5.3 KB(`-O2` text)로 v1(7.4 KB)보다 크다. 줄이는 것이
남은 일이다. 항목 text를 읽을 때마다 복사하는 비용도 남아 있다.