Skip to content

סקירת ארכיטקטורה

הבעלות בשני הצדדים

TSX / hooks          →  Mutation Stream  →   Scene / Layout / Paint
(מעטפת TypeScript)     בינארי, באצוות      (ליבת Rust, wasm)

נגן Canvas2D         ←   DisplayList      ←     Picture

המעטפת מחזיקה בעץ הרכיבים והליבה מחזיקה ב-Scene. השתיים אינן חולקות אובייקטים ניתנים לשינוי. כל תקשורת מעבר לגבול הזה נעשית בזרמים בינאריים עם גרסאות: little-endian, יישור לארבעה בתים, בצורת הוראות. הצד המקבל מאמת קוד פעולה, אורך, יישור, מזהים וחשבון לפני שהוא נוגע בזיכרון, וקלט פגום נדחה באופן אטומי במקום להיות מיושם חלקית.

הגבול הזה אינו אופטימיזציית ביצועים אלא גבול נכונות: גם כשהבתים מגיעים בדרך כלל מהמקודד של הפרויקט עצמו, המפענח מתייחס אליהם כאל קלט לא אמין, והוא מכוסה בפאזינג.

שני שעונים

שעון הממשק (התהליכון הראשי) ושעון הרינדור (ה-Worker) עצמאיים זה מזה:

  • התהליכון הראשי אוסף קלט, מריץ את עץ הרכיבים ומבצע commit לפריימים של Mutation.
  • ה-Worker מניע את פיזיקת הגלילה, האנימציה, הפריסה וההרכבה.

גלילה במצב יציב אינה קוראת למעטפת. נתונים חסרים מצוירים כמצייני מקום ומושלמים בפריימים מאוחרים יותר. לכן כשקוד היישום חוסם את התהליכון הראשי 200 מילישניות, הגלילה והאנימציה נשארות רציפות — תרחיש זה מוגן בבדיקות אוטומטיות של הזרקת תקלות.

שרשרת הנסיגה

זיהוי היכולות בוחר את נתיב ההעברה לפי הסדר, ושלוש הדרגות שקולות מבחינה תפקודית:

  1. SharedArrayBuffer — דורש בידוד בין מקורות (COOP/COEP)
  2. postMessage — כאשר אין SAB
  3. Canvas2D בתהליכון הראשי — כאשר אין Worker או OffscreenCanvas
ts
const root = await createHostedCanvasRoot(canvas, {
  transport: { preference: "sab" }, // העדפה בלבד; אם אינה ניתנת למימוש תתבצע נסיגה
});
console.log(root.mode); // "sab" | "post-message" | "main-thread"

ה-Playground של אתר זה הוא הדוגמה החיה: GitHub Pages אינו יכול לשלוח כותרות ‏COOP/COEP, ולכן הגרסה המפורסמת רצה בנתיב postMessage, ותג ההעברה בראש העמוד מציג זאת בכנות.

מודל ההשבתה

הסמנטיקה של כל prop קובעת את תחום ההשבתה. הקורא אינו מסמן דבר כמלוכלך ידנית, ואין דלת מילוט מסוג ‏forceUpdate.

כל תכונה מצהירה בסכימה בעלת מקור יחיד אם היא משפיעה על פריסה, ציור, בדיקת פגיעה או סמנטיקה. שינוי ‏opacity אינו גורם לפריסה מחדש; שינוי width כן. מפות הביטים של הלכלוך מנוהלות לפי תחום, ו-onFrame חושף את מספר הצמתים המלוכלכים בכל תחום.

הבחירה היא «השבתה צרה ככל האפשר, עם רשת ביטחון של בדיקות תכונה»: תוצאת הרינדור המצטבר חייבת להיות זהה לרינדור המלא פיקסל אחר פיקסל, ובדיקות דיפרנציאליות מכווצות כל דוגמה נגדית למקרה הכישלון המינימלי.

ייצוג ה-Scene

בתוך הליבה ה-Scene בנוי כ-SoA (מבנה של מערכים במקום מערך של מבנים):

  • מזהי הצמתים נושאים דור, ולכן מיחזור משבצת לעולם אינו מחייה מזהה שפג.
  • לאחר commit נשמר סדר טופולוגי: הורה תמיד מופיע לפני ילדיו.
  • דחיסת שינויים מבניים מתבצעת פעם אחת לכל commit, ולא פעם אחת לכל mutation.
  • תוצאות הפריסה מושוות באצווה מנתוני SoA עם חוצץ כפול, בלי הקצאת סגורים או מאזינים לכל צומת בנתיב החם.

עורף מתחלף

הליבה פולטת DisplayList בינארי ושטוח, והעורף אינו אלא נגן. עורף ה-Canvas2D הוא לולאה חסכונית בהקצאות מעל מערכים מוטיפסים — קריאת wasm→JS לכל פעולת ציור אינה נתיב רינדור קביל.

אותו DisplayList מוזן גם לאב-טיפוס מבודד מבוסס wgpu, ושתי התוצאות מושוות פיקסל אחר פיקסל. אימוץ WebGPU הוא החלטה מבוססת נתונים, ראה ADR-0006.

דטרמיניזם

זמן, מקור אקראיות וזרמי קלט ניתנים להזרקה או להרצה חוזרת, ופלט הליבה אינו תלוי בסדר התזמון של התהליכונים. ארכיון DOPR מקליט את זרמי ה-Mutation וה-Input בסדרם המקורי וניתן להרצה דטרמיניסטית ללא דפדפן, בסביבה חסרת ממשק: כך אפשר לשחזר מקומית תקלה מהייצור, בעוד זרמי עריכה רגישים מוחרגים מההקלטה במפורש.

להעמיק

האלגוריתמים, מבני הנתונים וקריטריוני הקבלה המלאים נמצאים במסמך התכנון הטכני.

מופץ ברישיון MIT