February 2012 - Posts
אחד האתגרים בתכנות מנוע תלת מימד זה בנוסף ל לדעת מה להכניס לכרטיס מסך זה לדעת מה לא להכניס לכרטיס המסך.
אל אף מהירותו וריבוי ה מעבדים ב GPU תחולת עיבוד גדולה תגרום להשהיות של ה Rendering thread כך שאיכות המוצג תיפגע עקב קצב רינדור נמוך (מספר frames לשנייה ).
אם נבחן scene מתוך ה Demo של SubDMesh

נראה שלצייר Scene בודד לוקח כ 50mSec .
בGraph שה Pix נותן לנו ניתן לראות היכן הרוב המאמץ והזמן עיבוד של ה GPU לוקח .כמובן בפקודת Render (מה שמיוצג על ידי החורים הלבנים ) בפרוט ה פקודות ניתן לראות את כמות ה Vertex ים הגדולה שכל פקודת Draw צריכה לטפל כמובן שהכמות מוכפלת עקב זה שה Demo משתמש ב Tesselation.
בכל מקרה בחרתי ב Patch divisions נמוך יחסית על מנת לא להכביד על ה GPUעל ידי יצירת הרבה Vertex חדשים כתוצאה מה Tessellation.


ב Scene שהודגם היה סך הכל חייל אחד מסכן שאני חושב שאפילו משרת אומנם בגבורה אבל בקריה. ב Sceneים אמיתיים כמות הMeshs בהרבה יותר גדולה .
כל Vertex , index שנכנסים לכרטיס מסך עוברים עיבוד דרך כל ה Shaders הפעילים ברגע הזנתם , רק בסוף ה ה Pipeline מתבצע ה Cliping של ה Data שאיננו מוצג .

ה RasterizerState קוצץ את כל ה pixels שלא נכנסים לאותו מלבן ש החלון שמוצג כלומר אם יש לנו scene שמורכב מאצטדיון כדור רגל שלם וכרגע רואים במסך רק שחקן אחד , רק ה pixel של אותו שחקן יגיעו ל pixel shader .
ה Z- test לסוגיהם מתבצעים בשלב יותר מאוחר כלומר אם אני מרדנר קריין חדשות מאחורי שולחן ל pixel shader יגיעו גם ה pixel ים של ה שולחן וגם של המכנסים הקצרים שלו (במקרה הטוב ) שלא נראים כלל .
כאן ללא כל קשר לטכנולוגיה שבה משתמשים בא התפקיד של התכנת למנוע ככול הניתן הגעה של vertex ים ו index ים שלא יגיעו לידי רינדור .כמובן גם פעולות אילו אשר מתבצעות ב Cpu יש להן מחיר מבחינת ביצועים ,אולי חלק מהן ניתן לבצע ב Thread אחר בכל אופן לבדוק בכל Frame כל vertex לפני שהוא מגיע לכרטיס מסך האם הוא Visibility זה דבר בלתי ישים לחלוטין !!!
LOD
מכיוון שאין שום סיבה שבתחום של 3X3 פיקסלים בו אמור להופיע מטוס קרב מרוחק ירונדר F22 עם מיליון Vertex ים נוצר מנגנון של LOD שמהותו לכל mesh שיכול להיות מרונדר ישנם מספר גרסאות שנטענות לכרטיס מסך בהתאם לקירבה של ה mesh לצופה .

ב Direct3d11 ניתן לבצע lod יותר חלק באמצעות tesselation .
View Frustum Culling
viewing frustum הינו החלק בעולם שהינו נראה על ידי המצלמה .

ה View frustum מורכב מ 6 משטחים כאשר כל מה שנמצא בתוכו הינו נראה על ידי המצלמה.
המטרה של הבדיקה הינה לראות האם mesh או חלק מ mesh נמצא בתוך ה View frustum ואם כן אז להזרים את ה mesh לכרטיס מסך.
ישנה דוגמא מאוד טובה ומאוד מאוד מאוד פשוטה אשר נלקחה מקורס תכנות גרפיקה ממוחשבת לתלמידי מדעי החברה באחת מהמכללות .את הדוגמא אפשר למצא כאן.
הדוגמא מבצעת 3 פעולות מרכזיות במטרה להחליט אם אוביקט נימצא ב View frusum
1.הדוגמא מגדירה את 6 ה planes שמרכיבים את הצלעות של ה frustum של המצלמה .הקוד הינו קוד סטנדרטי ביותר שאין צורך להמציאו .
2.עבור כל סוג של אוביקט תלת מימדי הדוגמא מגדירה פונקציה שיודעת לחשב האם האוביקט נחתך \ מוכל בתוך ה frustum או נמצא מחוצה לו
דוגמא לפונקציה כזו עבור כדור הינה :

3.הדוגמא מרנדרת רק את האובייקטים שנמצאים בתוך ה View frustum או נחתכים אייתו.
מכיוון ש mesh הינו צורה יותר מורכבת מכדור או תיבה ואם נתחיל לבדוק כל vertex ב mesh כל Frame הcpu יחנק לנו בד"כ בונים תיבה חוסמת ל Mesh ובודקים האם היא נחתכת או נמצאת בתוך ה View frustum .

החישוב של ה מלבן החוסם את ה mesh יכול להיות מבוצע על ידי שימוש ב פונקציה D3DXComputeBoundingBox שהינה חלק מה Dx sdk .
לפני שנים רבות הייתה המלצה להשתמש בכדור חוסם במקום במלבן חוסם עקב זה שמספר הבדיקות של מלבן חוסם גדול פי 6 מאשר כדור ,היום בcpu ים המודרניים גם בדיקה של מאות מלבנים חוסמים לא מורגשת .
ניתן לבצע אופטימיזציות על החישוב ,רוב האופטמיזציות מכוונות למצבים בהם רק חלק מה mesh מופיע ב frustum . ניתן לחלק את ה mesh ל Quad trees (ב gpu gems בפרק של הterrain יש הסבר איך לעשות את זה ) כאשר מרנדרים terrain הדבר מאוד חיוני , יש מאות אלגוריתמים סטנדרטים לTerrain (דוגמא) לגבי mesh ים בודדים כדוגמת מטוס לא בטוח שהטרחה משתלמת.
כאשר ה meshהינו מסוג Hierarchical Bones Mesh ניתן לבצע את החישוב של איזה חלק מה mesh יכנס ל frustum באופן רקורסיבי .

וכל זה הקדמה לבאג שהופיע אצלנו לפני כשבועיים :
כאשר היו מטיילים עם המצלמה לאורך אחד הצירים היו מבחינים במעין קפיצה של mesh ים קרובים כאשר ה Mesh היה אמור להיכנס לתמונה ,מאין השהייה של כמה מאות מילי רק ל Mesh ואז בבת אחת חלק ממנו הופיע הוא המשיך להיכנס חלק לScene באופן חלק .
לאחר חיפוש הסתבר לנו הדבר הבא :
מכיוון שה Mesh היה מרונדר באמצעות Tessellation map ו Tessellation maps בניגוד לכל ה Maps לסוגיהם (Light , hights וכד) בעל יכולת הרחבה של ה mesh מעבר למימדים שלו . ההרחבה בוצעה רק בGPU וכאשר חישבנו את ה frustum culling ב CPU הדרנו את הMesh למרות שחלק ממנו כבר היה צריך להיות בתמונה .
הפתרון מבחינתנו היה לתת למנגנון ה frustum culling מקדם עבור כל mesh שבאמצעות הfrustum מאין מוגדל עבור mesh ים מסוימים בקרבה ל מסך .
אם נביט בתמונה הבאה אפשר לראות שיש חלק שלם של מים ליד הגלגלים שנוצר על ידי מנגנון ה Tessellation ב gpu ואיננו נכלל בחישוב של ה frustum culling המתבצע ב cpu .

הדרישה
באחד מהפרויקטים עליהם עבדתי בעת האחרונה היינו צריכים לבצע ארכיטקטורה של שכבת DAL עבור אפליקציה שמכילה מספר רב של סוגים של ישויות מידע שונות ביילתי קשורות או תלויות זה בזו כמעט לחלוטין .כלומר לפעולות join לסוגיהן בין סוגי ישויות המידע אין קשר לוגי .
על ישויות המידע השונות יש לבצע פעולות Cruds כתיבה חיפוש עידכון ממחיקה , Full text search וכד
כמות הפריטים בכל רשימה של ישויות מידע נעה בין בודדים לאלפיים ,כאשר דרישה נוספת זה שהסכמה של ישות מידע הינה דבר דינמי שיכול להשתנות עם הזמן .
ישויות המידע כתובות כ Poco objects בלתי תלויות כאשר ישות מידע יכול להיות מורכבת מ Class ראשי שמכיל מספר Classי משנה.

חשבנו לבחון אפשרות של שימוש ב NoSql Database מסוג Document כ Repository משום ששימוש ב RDBMS גם מצריך הרבה עבודה של יצירת הרבה מאוד טבלאות , שינוי סכמות דחופות בניית שיכבת dal מורכבת ואין הנאה מהיתרונות של RDBMS הן בגלל גודל יחסית קטן של הטבלאות והן בגלל שאין קשר בין הטבלאות .
(אפשרות נוספת שחשבנו עליה היתה שימוש ב code first של אחד מה Frameworks )
ה NoSql Document Db שבחרנו לבחון הינו RavenDB .
RavenDB הינו NoSql DataBase שכתוב ב net. ששומר מסמכים בתצורת JSON documents שתומך בטרנזקציות , RavenDB רץ כServer כאשר הממשק אליו הינו באמצעות שאילתות Linq . ניתן למצא פרטים על RavenDB הן באתר ,מאמר ב msdn וגם בסדרת פוסטים בנושא כאן וגם כאן.
היתרונות
-
פשטות התממשקות וכתיבת שיכבת הDal , Interface חושף RavenDB טוב שעונה על ההגדרה של Interface אידיאלי בחלק של פשוט מאוד לעשות איתו דברים פשוטים בחלק של אפשרי לעשות איתו דברים מורכבים אני לא כל כך הסתדרתי אולי זה נובע מחוסר תעוד .
-
פשטות הפעלה ותמיכה ב DB .
-
ביצועים מעולים בכל הפעולות שניסינו גם בכמות מידע שגדולה בכמה סדרי גודל ממה שאנו צריכים .
-
תמיכה מעולה של החברה גם בstackoverflow וגם בקבוצת הדיון .
-
מחיר יחסית זול ל RDBMS ים
6.ממשק ניהול נוח .המאפשר לבצע גיבויים לראות את הלוג , למחוק , לראות את כל הdocuments ב Database , ניהול index ים .
דוגמא למסך מתוך ממשק הניהול בו ניתן לראות את ה Documents השמורים , לבצע חיפוש להוסיף ידנית .

החסרונות:
-
כלי ניהול כדוגמת גיבויים אוטומטיים , שאילתות online לא מפותחים מספיק , למרות שלצרכים שלנו ניתן לתפור את הכלים די בקלות לפי הדוגמאות המסופקות עם החבילה .גיבויים ידניים המערכת מאפשרת לעשות באמצעות ממשק הניהול בקלות.
-
אפשרות הרחבה לעתיד של ה Storage לצורכי BI לדוגמא על מנת לשמור את כל הפעמים שהליך רץ ותוצאת הריצה וחיפושים ותובנות ,הדבר אפשרי אבל מהר מאוד זה מתחיל להיות יותר מסובך מאשר שימוש ב sql db שמיועד למטרות אילו.
-
חוסר תעוד רישמי למרות שאפשר למצא המון מזור באתרים כגון Stackoverflow וכד
-
חוסר בדוגמאות (תמיד חסר לי אולי ב WCF נתקלתי בכמות מספקת ).
-
נושא ה Security לא מפותח מספיק לצרכינו לטעמי .
-
תומך בעיקר בעולם ה net. דבר שבעיתי אצלנו בעיקר באפליקציות שכתובות בpython , java , php .
-
חסרה יכולת לשמור ערכים עם Referance מעגלי של אוביקט אחד לשני ובחזרה .
התקנה והרצה
לאחר שהורדנו את החבילה מהמרשתת על מנת להפעיל את הdb זה להפעיל את ה start batch file (דבר שפותח אוטומטית את ממשק הניהול הסילוורלאיטי ).
על מנת להתחיל לפתח יש לחבר מספר assemblies ל project להעתיק מספר שורות קוד ואתם בפנים .

Open DocumentStore and Session

הממשק מול ה RavenDB מורכב שני אובייקטים מרכזיים :
ה Docuemnt store שמכיל את הממשק ברמה של הDataBase .הוא עוטף את אפשרויות היצירה שלה Session , התחברות ל Db instance
שמירה של אוביקט חדש
השמירה מעדכנת את ה ID של האוביקט הנשמר כאשר מבנה הid אם הוא לא מסופק על ידי הצרכן מחולל בtemplate שמורכב משם הclass עם סיומת רבים + ה id החדש .(המערכת יודעת מבחינת אנגלית להוסיף נכון את הסיומת )

אין צריך להגדיר ב DB את ה Scema של האובייקט או את התיקיות הכל מוגדר אוטומטית.
עדכון אוביקט
על מנת לעדכן אובייקט ב Database יש לבצע את פעולת הStore עם האוביקט המעודכן כאשר ה ID מכוון לID שאותו אנו רוצים לעדכן ,כל שינוי גירסה מעדכן את הערך של ה etag ואת זמן העדכון .
שאילתות ל DB
כאמור שאילתות מתבצעות תוך כדי שימוש ב Linq .
RavenDB עבור כל שאילתה בונה index דינמי בהתאם לתוכנה של השאילתה למטרת החזרת התשובה ,לשאילתות שכיחות RavenDB בונה index סטאטי שמתעדכן ב thread נפרד ,כאן יש עניין של נעילות אם רוצים לקבל את המידע המעודכן ביותר לאותו הרגע .
דוגמה לשאילתה :

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

http://www.gamlor.info/wordpress/2011/07/ravendb-queries-and-indexes/
שונות
RavenDB תומך ב Attachments של קבצים בינארים (Blobs) כדוגמת קבצי תמונה או אודיו .
ניתן להרחיב את החבילה הביסיסית של RavenDB על ידי Plagins .
יש אפשרות לבחור מנוע אחסון כ Embedded ואז הקריאות ל DB מתבצעות באופן לוקלי ולא דרך ה network , כמו כן ניתן להגדיר שהנתונים ישמרו באופן ארעי בזיכרון RunInMemory = true
שיכבת ה DAL
המטרות שלנו בארכיטקרטורה של שיכבת ה Dal הינן:
- כמה שיותר Decoupling משיכבת ה BLL כך שאם נצטרך מסיבה זו או אחרת להחליף את מנוע ה Storage הנגיעה בשיכבת ה BLL תהייה מינימלית .
- אין צורך שהמערכת תוכל לתמוך בשני מנועי אחסון שונים בשינוי קונפיגורציה .
- זמן עבודה בערך כ 50 שעות (בחיים האמתיים זמן עבודה הינו אלמנט די מכריע בתכנון ארכיטקטורה )
עקב זה שלא נראה שיהיה צורך שהמערכת תוכל לתמוך ביותר ממנוע אחסון אחד שכבת ה Dal יכולה להיות קונקרטית לטכנולוגיה ,וכאשר מסיבה זו או אחרת ירצו לתמוך במנוע חיפוש אחר יבצעו פעולת חציבה ורמונט כללי בassembly של הDAL.
Components diagram

ה Components מהם מורכבת שכבת ה Dal יכולים להיות מקובצים לפי מידת הקונקרטיות לטכנולוגיה כאילו שימחקו ויכתבו מחדש עכב שינוי טכנולוגית האחסון וכאילו שרק יעברו שינוי קל לצורך התאמתם לטכנולוגית אחסון אחרת.
השיכבה יכולה להיצרך ישירות או עלי ידי תפירתה בקלות כ Web Service.
Class diagram

בדיוק כפי שאני מעדיף ששכבת ה Dal תבצע שאילות SQL או תקרא ל Sps ככה אני מעדיף ששאילתות הlinq יהיו חלק משיכבת ה Dal ול BLL יחשף interface לוגי .


הנושא של ארכיטקטורת ה Data containers נושא בחובו דילמה האם להשתמש באותם DataContainersלאורך כל השכבות מה Dal עד ל UI , מה שגורם להם:
- להיות עטופים בattributes שונים שכל אחד קשור לשיכבה אחרת.
- ,לממש interface ים משכבות שונות
- ולדאוג ל Convertors לדוגמא לשיכבה של ה Dal לא מתאים member מסוג Bitmap אלא מערך של Bytes .
אפשרות שניה עבור כל שיכבה לייצר DataContainers משל עצמה כאשר אפשרות זו נושאת בחיבה עבודה של כתיבת Convertors שמציבים את הDataContainer של שיכבה אחת בשניה .
עבור כל DataEntity נכתב accessor קונקרטי שממש את ה interface הספציפי שהוגדר עבור אותו DataEntity .
פעולות משותפות הוגדרו בclass משותף

אלמנטים שמתפעלים את הRavenDB
CustomSerializators – שממירם לנו אובייקטים שלא ניתן אוטומטית להמיר ל JSON
DbOperationsAccessor – מכיל פעולות כמו מחיקת DB , רפליקציה שממומשת על ידנו , יצירת DB
RavenDbSessionWrapper שעוטף לנו את הקריאה גם ל Document Store , ל Init פניה ל configuration להביא את ה port ושם ה DB
RavenDbDataIndex – הגדרות של index ים סטאטים

מסקנות
ראשית נחשפנו לטכנולוגיה של NoSql Document DB .
העמדנו מערכת בזמן קצר יחסית עם ביצועים משביעי רצון .
חסר לנו הקטע של Security כנראה שהתרגלתי לאושר של sql server וכד הן מבחינת אפשרויות הlogins וה roles דברים שהיו חסרים לי כאן .
חסרה לנו התובנה כיצד לבצע שאילתה שמכילה Search על text למרות דוגמאות רבות לא הצלחנו בזאת .
בגירסה הבאה של .wcf 4.5 יכלל גם transport חדש שתפקידו לתמוך ב Udp .
הצורך ב udp transport היה אצלנו כבר כמה שנים הסיבות לכך היו :
- אסינכרוניות מלאה אין מצב ל Dead locks .
- תעבורת רשת יותר קטנה מאשר שאר ה transports חשוב כאשר יש תעבורה גדולה ורוחב פס קטן .
- ביצועים :זמן שליחת , קבלת הודעה בהרבה יותר טוב משאר סוגי ה transports .
בד"כ משתמשים ב udp transport במקרים בהם דרושה יכולת של שגר ושכח לדוגמא מקרר בסופר שמדווח כל פרק זמן על הסטאטוס שלו האם הדלתות פתוחות , הטמפרטורה וכד. לא מעניין את המקרר האם מישהו מאזין אליו , ולא מעניין את המאזין תוכן ההודעה הקודמת אם הוא כבר קיבל הודעה חדשה .

כאשר יש רק סוג אחד של הודעה שנשלחת באופן מחזורי ,אפשר לתהות מה ה Add in values ש Wcf נותן לנו על פני סתם המרה של האוביקט מידע למערך של bytes ושליחתו באמצעות udpclient .כאשר ה Contract נהייה מורכב ,ויש דרישות נוספות כגון security , Discovery , routing מתחילה להיות משמעות לשימוש ב wcf .
על מנת לספק את הדרישה הזו השתמשנו ב Udp transport שסופק כדוגמא ב sdk .הדוגמא מבחינת ה interface שהיא חושפת מאוד דומה לudp transport של 4.5 .
הדוגמא מספקת לנו udp transport channel כאשר ה Channel עצמו ממומש כ IInputChannel ו כ IOutputChannel.


החלקים המעניינים הינם כל הטיפול ב Socket ב Class ים האילו ב OutputChannel בפקודת Send אשר מבצעת סיראליזציה ל Message object וקוראת ל פקודת SendTo של ה Socket

ב input channel המימוש קצת יותר מורכב.
ב פקודת OnOpened של UdpChannelListener נוצר ונפתח udp socket עבור כל endpoint ש ה Service חושף.
לכל socket שנוצר מתחילים להאזין באופן אסינכרוני באמצעות הפונקציה ContinueReceiving .

כשמגיע הודעה חדשה ההודעה החדשה נדחפת לתוך Queue אשר תפקיד ה UdpInputChannel לקרא ממנו את ההודעות ולשלוח אותן ל Dispatcher .
בכוונה הדגשתי את הקטעים שקשורים ל udp socket שאר הקוד מטפל הן בהודעות סינכרוניות והן אסינכרוניות ומהווה בסיס מצוין לפי דעתי לכל צורך למימוש Transport channel החל מ Rs232 וכלה במנגנון שמפריח יונות דאר .
מכיוון שהדוגמה מתעסקת רק עם ה transport channel עדיין נשארים כל המנגנונים שלא קשורים באופן ישיר לתעבורה לדוגמא הEncoder ו הDecoder של ההודעה איזה encoder שרק תבחר או תממש כאן קביל, אפילו תקודד את ההודעה למורס אין שום קשר ל transport

ה encoder הינו class שיורש מ MessageEncoder ואפשר לצינו הן בקוד או בconfiguration .כאשר מגדירים את ה Binding בדוגמא משתמשים ב TextMessageEncodingBindingElement.
הדוגמא מדגימה איך ניתן לבנות את הChannel stack כך שיתקבלו תכונות שאינן אפשריות עקב האופי ה אסינכרוני של udp .
לדוגמא:
- פעולה סינכרונית הClient קורא ל Server וממתין לקבלת תשובה .
- ניהול wcf sessions
- Reliable messages - ה Client שולח הודעה ומחכה timeout עד שההודעה מתבצעת , יכול להיות מצב שה Client שלח את ההודעה כאשר ה Server בכלל למטה ואז כשהריצו את ה Server אם לא חלף לו ה timeout אז הפקודה תתבצע .
הDemo על מנת לתמוך בפעולות אילו מוסיף ל Channel stack את ה ReliableSessionBindingElement.
על מנת לתמוך ב Duplex channels ה demo מוסיף ל Channel stack את ה CompositeDuplexBindingElement.
גם תמיכה ב טרנזקציות אפשרית והיא מודגמת באמצעות הDemo של TransactionMessagePropertyUdpTransport
תמיכה ב hosting ב IIS מודגמת באמצעות ה Demo של UdpActivation.
אחד המקרים שצריך את התכונות האילו ב udp transport הינה כאשר משתמשים בudp כ Interoperability למערכת צד ג שהתעבורה אליה הינה udp והיא מתנהגת באופן סינכרוני .
יש לזכור שכאשר עובדים עם Relaible session מקבלים הפחתה ניכרת בביצועים .
חלק ניכר מ Frameworks ,ארכיטקטורות לסוגיהן כדוגמת MVVM , Spring , MF , מגדירות מסגרת שהארכיטקטורה של המערכת צריכה להתיישר על פיה.
לדוגמא ב mvvm הקשר בין ה modal ל view צריך להיות דרך ה modalview ופנייה ישירה מהview ל modal כדוגמת הירשמות לevent של click של button ומימוש ה event ב xaml.cs שוברת את הארכיטקטורה על אף שלכאורה הכול יעבוד.
לארכיטקטורה כזו מספר יתרונות שהמודגשת ביותר זו כמובן ה Decoupling בין השכבות כך שבעולם אידיאלי ניתן יהיה לשנות את ה view מבלי צורך לשנות את שיכבת ה UIL .בעולם האמיתי זה יותר בכיוון שלתכנת יהיה קצת יותר קל להבין את הקשר בין שיכבת ה UIל UILופחות מסובך לשנות את שיכבת הUIL כך שתתאים ל view החדש .
אחד היתרונות שלפי דעתי יותר משמעוותים שארכיטקטורה כזו נותנת זה אותו השלד של ארכיטקטורה שרק צריך לעטוף בשכבה כמה שיותר דקה של לוגיקה עסקית קונקרטית.
יותר פשוט לעשות ארכיטקטורה לפי כללים מסוימים לדוגמא בשכבת ui המבוססת על mvvm הארכיטקט יודע שהוא צריך להגדיר את ה modal ודרך זרימת הcruds אליו , הוא צריך להגדיר את הmodalview ואת ה forms ו ה user controls של הView .
עדיין יש מרחב התקשקשות ואין שבלונה קבועה אבל זה עדיף בהרבה ממצב בו מתחילים ממצב נקי לגמרי בשיא ההתלהבות וחושבים על פיתוח כל מיני תשתיות גנריות שישמשו גם לעתיד וישום כל מיני patterns תאורטיים והמערכת יוצאת מסורבלת וכל מה שחושבים לגבי העתיד זה מתי ניתן לזרוק את המערכת ולכתוב הכול מחדש .
החסרונות של אותה ארכיטקטורה המבוססת על framework
גמישות – דברים לא מורכבים בדרך כלל קל לעשותם עם אותו ה Framework דברים מורכבים לפעמים ההשקעה שצריך במערכת שמפותחת באמצעות framework גדולה אין ערוך מזו של עבודה ללא ה framework .
לדוגמא : מימוש GraphView של nodes במקום TreeView בMVVM .במצב ערום ללא framework הדבר מורכב אבל אפשרי , כאשר מנסים ליישם את זה באמצעות mvvm ומתחילים לחפש איך לרשת מ HierarchicalDataTemplate , איך לשנות שיהיה אפשר שchild יהיה משותף לכמה אבות כמות ההתעסקות בכיפוף הטכנולוגיה הינה אדירה. עד כי נראה כי ניתן להחליף לנזירים ממגדלי הנוי את העיסוק היום יומי .
אי תמיכה – מחר מיקרוסופט או חברה אחרת יכולים להחליט שהם מזיזים הצידה טכנולוגיה זו או אחרת ואז מה ? קוד לוגי שאיינו מסתמך על טכנולוגיה בהרבה יותר פשוט להעביר לדוגמא מ C# ל java מאשר workflow של wf לאיזשהיא טכנולוגיה מקבילה או לקוד .
Technology awareness – עצם הקישור לטכנולוגיה מגבילה למה שהטכנולוגיה יכולה להציעה לדוגמא אם השתמשנו ב mvvm שקשור ל wpf יהיה קשה לנו להכניס טופס ישן מבוסס על winforms.
מוטיבציה
ברגעי משבר שאתה מתחרט שלא שמעת בקול של הוריך שרצו שתהייה מציל בחוף שרתון ואתה התעקשת ללכת לעבוד בהי טק כי יש שמה מכונות קפה שוות בא לך לעשות double click על כפתור בEditor של ה טופס וליצור event ולממש שם את התוכן שלך בקצרה במקום להתחיל להגדיר commands ו bindings ו לשים את הcommand ב context .
שימוש ב Framework כזה או אחר דומה לטיול מאורגן לחו"ל מאוד נוח רואים יותר דברים לא מבזבזים זמן על התעסקות בדברים אדמינסטרטיבים אבל בכל זאת מפסידים משהו מהחוויה.
WF Framework
WF לא נותן יכולת טכנולוגית שלא היתה קיימת קודם או שהיה צריך להתאמץ מאוד על מנת לישמה נהפוך הוא הרבה מאוד פעמים ליישם בקוד דברים יותר פשוט מאשר מwf .
WF נותן מסגרת למימוש תהליך עסקי , מה שחוסך צורך בהגדרת ארכיטקטורה אם אפשר להתיישר לפי wf .
אחד השימושים שאנו משתמשים בטכנולוגיה זו הינה יצירה דינמית של bl workflows .
דוגמא דומה לישום שלנו הינה מכונה שיודעת להכין עוגות לבד בהתאם למתכון שהוזן על ידי המשתמש . (כמובן שHighlight שלה זה עוגת שוקולד עם ספותה שחורה ).
השף מגדיר באמצעות מסך גרפי את התהליך של אפיית העוגה הוא מגדיר חומרים לעוגה , תהליכים באפיה בדיקה עם קיסם עץ אוטומטי האם היא אפויה וכד.בסיום התכנון השף שומר את התוכנית בבסיס נתונים ,התוכנית תשמש במכונה לצורך הרצת תהליך האפייה .
למרות ש wf תומך ומעודד שימוש בDesigner שלו לצרכי בניית ה Wf (דוגמא DesignerRehosting בsdk ) מנסיון שלנו לא טוב לנסות קשור בין ה Ui של ה wf editor ל UI של משתמש הקצה אלא להתאים את הui לuse case של המשתמש ולא לטכנולוגיה של שיכבת הBLL .כמו שלא ניתן לשף מסך לעשות joins בין טבלאות למרות שאפשר להציג את זה בצורה מאוד נוחה למשתמש .
מה גם שהeditor של wf עדיין לא מפותח דיו ואיננו מאפשר קסטומיזציה טובה .לדוגמא בגירסה הנוכחית הוא אינו תומך בcustom activity designer באפשרות להכניס control אחר מאשר edit box לדוגמא combobox ולאפיין את הקישור אליו.(כפי שהבנתי בגרסה הבא מיקרוסופט אפשרו את היכולת הזו ).
המערכת אצל המשתמש מפיקה את ה workflow באופן דינמי ושומרת אותו בבסיס נתונים.
יתרונות של הארכיטקטורה הינן :
הארכיטקטורה מפרידה תהליך מאוד מורכב של קליטת נתונים ממפעיל והפעלת מכונה עם אותם נתונים לשני חלקים :
חלק סטאטי שהמכונה בזמנה החופשי מייצרת workflow על פי הנתונים של המפעיל . כאשר הworkflow שמיוצר הינו מינימליסטי ומאוד קונקרטי לטובת תהליך אחד בלבד.
חלק דינמי שהוא יותר real time שמריץ את הworkflow שיוצר מראש .
תהליך דביגאבילי :
ניתן להשתמש בכלים שה wf חושף בצורה מיטבית הן להציג את התוכנית לפני הריצה והן תוך כדי ריצה לראות את התקדמות שלה .

לWF ישנן יכולות tracking מאוד טובות שהינן Build in .
שימוש ביכולות הWF מובנות של
כדוגמת :
Cancelation ו ב Compensation
אם באמצע תהליך אפיית העוגה מסיבה מסוימת מחליט האופה לבטל את התהליך או שלוגיקת התוכנית עצמה קוראת ל Cancel
יכולות אילו נותנות אפשרות להגדיר Activities אשר ירוצו הן במקרה שActivity הסתיים בהצלחה ובהמשך הריצה נקרא Cancel ואז צריך מאין להחזיר את הגלגל אחורה ולבצע פעולה מנוגדת לפעולה שכבר הסתימה בהצלחה כמו אחרי הזמנת כרטיסי טיסה אם המערכת מבטלת את התהליך כתוצאה מאי אישור של מנהל אזי יש לבצע פעולת פיצוי של ביטול ההזמנה .
ונותנת גם אפשרות להגדיר ל activity סוג של תהליך שיקרה כאשר הcancel נקרא כאשר הוא במהלך הריצה ולא השלים את עבודתו .
תשתיות הWF נותנות יכולות לכלול השהיית התהליך והמשכתו בשלב מאוחר יותר .
טרנזיטיביות זה לא בדיוק תכונה נתמכת במערכת עקב החוק השני של טרמומכניקה שקצת קשה להפריד חזרה את הביצים , קמח והגזר מהתערובת של העוגה אם לא הצלחנו בתהליך הדלקת התנור .
Use case

המפעיל של המכונה טוען את הקובץ הworkfow נטען ויכול להריצו ביחד עם פעולות כמו לבטל הרצה , לראות את התוכנית של האפייה וכד

אבני הבניין העיקריות של הארכיטקטורה

Class diagram
Custom activities
אילו Activities קונקרטיים שמהווים את אבני הבניין לWorkflow שיחולל אוטומטית.

הערות
1.הDesigners של ה Activities כתובים ב Wpf ,הבעיה המרכזית איתם בגירסה 4.0 הינה העדר יכולת להכיל controls לעריכת Arguments או variables שאינם EditBox ואז לדברים מורכבים כגון list של אלמנטים בלתי אפשרי לתת אפשרות עריכה .
למיקרוסופט יש פיתרון פנימי לבעיה הזו הוא משמש לדוגמא ב Send activity לצורך מילוי השדות של הפרמטרים , לא הצלחתי להעתיק את הפתרון שלהם לאפליקציה שלי.
ניתן לקשור לXaml בBinding ערכים של modalview אשר ישמשו בזמן debuging להצגה אקטיבית של state של הריצה .לדוגמא: במקום הDesigner הסטנדרטי של Delay activity ניתן להחליפו בDesigner שמראה זמן יורד תוך כדי ריצה .
2.על מנת לבצע פעולה ממושכת כמו דימוי האפייה כמומלץ אנו לא ממשים בתוך ה Bakeing activity סוג של while loop עם sleep ובדיקה מחזורית,אלא משתמשים בextention .
כאשר הactivity מסיים את פעולות האתחול שלו הוא נעצר בbookmark ואז extention ששייך לו מבצע את הבדיקות וכאשר התנאי מתקיים הוא משחרר את הbookmark וה Activity יכול להמשיך בעבודתו .
הדבר נועד לאפשר את תהליכי ה Cancelation וכד

הקוד:


ברוכים הבאים לפרק השלישי של הטרילוגיה שעוסקת בשינוי ה data packet שזורם לרשת או ממנה באמצעות Ndis filter driver.
לצערי בגירסה הנוכחית של NDIS עדיין אין תמיכה בשאילתות Linq ואו בReflection לכן את העבודה שלה parsing של ה NBL עלינו לבצע באמצעות פקודות וmacros של wdm ו ndis .
מבחינת תוכן לוגי ה NBL בהודעת udp בנוי בצורה הבאה :

חלק של Header הודעת Ethernet
נציג של שיכבת הקו בosi .
חלק שזה מכיל את ה Mac של כתובת המקור ,
ה Mac של כתובת היעד .
ופרוטוקול המידע של ההודעה לדוגמא: ip4 ,ip6 , arp , Icmp וכד

חלק של Header הודעת Ip4
נציג של שיכבת הרשת ב osi
מכיל את הנתונים הבאים :
גרסת הפרוטוקול ,
אורח הפתיח ,
סוג השרות ,
הגודל הכולל של החבילה ,
מספר הזיהוי של החבילה שמשמש כאשר הודעה מורכבת ממספר חלקים
(TTL - Time To Live).
הפרוטוקול שבאמצעותו מעבירים את המידע לדוגמא : tcp , udp ,
כתובות ip של שולח ההודעה וכתובת ip של הנמען הסופי .

חלק של Header הודעת Udp.
נציג שכבת ה תעבורה ב OSI
מכיל את פורט ה מקור ופורט היעד אורך החבילה וה Checksum .

payload , Data
המידע עצמו שנשלח או התקבל ברשת לדוגמא הודעות soap , http ,smtp וכד

מבנה המידע בהודעת udp
באיור מוצג structs שמרכיבים את מבנה של הHeader המבנה מתאים להודעת udp בלבד ,כל הודעה אחרת המרכיבים שלה יהיו שונים .
כל הודעה מכילה מפתח שמצביע על סוג ההודעה הבאה על פיו אני יודעים לדלות את המידע של הheaders המתאימים.

מבחינת פיזור המידע ב NBL המידע ערוך בצורה הבאה :

Object diagram

מציאת הFirst Network buffer מתבצעת בצורה הבאה

להודעת udp יש שני Buffers הראשון של הheader והשני של המידע .מציאת הBuffer ים של המידע מתבצעת בשיטה הבאה :

בקוד הבא ניתן לראות את הדרך בא מוצאים את ה packet הראשון של ה Header .
ndis הינו little indian כלומר צריך להפוך את מיקום ה Byts בStructs כאשר מלבישם עליו מערך של bytes

משתמשים בפונקציה RtlUshortByteSwap על מנת לבצע את ההצרחה של ה Bytes.
הקוד שמוצא את הpattern ומחליף ערך הינו :

אנו מוצאים את הpattern באמצעות NdisEqualMemory ומחליפים char אחד בו .
בUdp אין צורך לעדכן Check sum
כלי עזר מאוד חושב הינו ה Wire Shark , יש לשים לב שwire shark איננו עובד במקביל ל Kernel debuging
הרבה מאוד פעמים structs של אוביקטי רשת שמורידם מה internet אינם מדויקים ובאמצעות wire shark ניתן לבדוק ולהשוות לשמה בdriver .
את הקוד ניתן למצא כאן
באחד מהפרויקטים שעבדתי עליהם לאחרונה היתה דרישה להחליף תוכנת עמדה במערכת מבזורת בתוכנה אחרת .
התקשורת בין כל העמדות הינה WCF ,כאשר הcontract של העמדה החדשה שונה לחלוטין מהcontract הקיים ואין אפשרות להחליפו לזה שהיה קיים .(העמדה הייתה ונשארה server )
ל Contract החדש אין תאימות חד חד ערכית ברמת הקריאות כלומר חלק מהקריאות של ה Client היו צריכות מספר קריאות בContract של ה Server החדש חלק היינו צריכים לשמור ב Cache על מנת לאגד מספר קריאות מה client לקריאה אחת בServer וכד.

חשבנו על 3 פתרונות אפשריים
פתרון 1 מערכת חיצונית
לכתוב או לקנות מערכת חיצונית שהינה מאין biztalk sever אשר יבצע את פעולת התרגום .
יתרונות
מבחינת decoupling מאוד גבוה בין מנגנון ההמרה לאפליקציה , השינוי היחיד שנדרש לבצעו באפליקציה זה החלפת ה address של ה wcf server .
אפשרות ניטור של המידע הזורם + העבודה המתבצעת באותו מתאם .
תמיכה יכולת גידול לעתיד לdevice ים או שיטות תקשורת שונות .
חסרונות
הבעיה שצריך לתחזק עוד אפליקציה \ service שאיננו חלק אינטגרלי מהאפליקציה .(תיעוד התקנה קיטלוג )
לקנות אפליקציה חיצונית עולה כסף .
Hop נוסף של מידע שאין בו צורך . המידע נשלח ל wcf service ולאחריו ל עמדה הנוספת.
פתרון 2 השיטה הסינית
שיטת הquick and drity לאתר את כל המקומות בקוד שפונים לwcf client ולשתול בכל המקומות switch ים של האם לשלוח בintrerface החדש או הישן . + להוסיף את כל הלוגיקה בתוכנה עצמה של ה Client .
יתרונות
למרות שיש הרבה מאוד עבודה העבודה פשוטה אין סיכון טכנולוגי .
חסרונות
מכיוון שבאפליקציה אין שיכבה אחת מסודרת שקראה לwcf clients אלא הקריאות התבצעו מכל מקום אפשרי וישנם מאות מקומות כאילו הקוד של האפליקציה יסרבל עוד יותר כי זה לא רק הקריאה עצמה יש להוסיף לוגיקה לכל קריאה .
כמובן שאפשר לעשות את זה יותר אלגנטי ולא להשתמש בSwitch אלא שימוש ב Factory שיחזיר Iterface ל ממשק .
הפתרון ה 3 Wcf custom channel
פתרון באמצעות Custom protocol channel שכתבנו שתפקידו לתרגם את ההודעות .
יתרונות
פתרון שהינו מאוד decoupled אין צורך לפתוח אף אחד מהתוכנות . אפשר באמצעות קונפיגורציה להכיל אותו .
לא נוצר עוד hop מיותר
מאתגר מקצועית
אפשר באמצעותו ללמוד wcf לעומק
מאוד מרשים ראשי צוותים סמי מיקצועיים (כאילו שיועדים מה זה wcf בשתי מילים אבל אין להם שמץ ניסיון בארכיטקטורה נכונה וכד )שאתה מספר להם ברעיון עבודה שעשית כזה דבר חושבים איזה ילד פלא אתה ,השועלים הותיקים לא יתנו לך לסיים את הטרילוגיה והם כבר יראו לך את הדלת. שתסבך עד מוות את הפרויקט של מישהו אחר .
חסרונות
מאוד technology aware כלומר מבוסס על יכולות ההרחבה של wcf ותלוי ביכולות האלו . ומכיוון ש wcf איינו קוד פתוח ניתן להיתקע עם memory leak בעיית ביצועים או חלקים שבלתי ניתנים למימוש .
Wcf channels
התשתית של wcf מכילה ערימה של channels המתפקדים כ chain of responsibility
דומה מאוד ל ndis filter drivers .

בין כל החלקים של ה channel זורמים אוביקטים מסוג Message .
הMessage
מורכב מ Headers וPayload של המידע (הAction והפרמטרים שלה ).
כל channel יכול להוריד להוסיף או לשנות את ה header ים .
כמו כן ה Channel יכול לשנות את ה payload כלומר את המידע עצמו שעובר ב Message
(שינוי פשוט של מידע יכול להתבצע באמצעות Behaviors כדוגמת מימוש IOperationBehavior ביתר קלות )
הפתרון שלנו התבסס על זה ש Channel במקרים מסוימים יכול להחליט שהוא לא מעביר הלאה הודעה (כלומר חוסם אותה ) או מעביר כמה הודעות במקומה ) או מעביר כמה הודעות בזמנו החופשי בthread אחר אחרי שההודעה בכלל נסתיימה .
הפתרון הזה עובד טוב רק ב interface אסינכרוני .בinterface סנכרוני המצב מסתבך כי המערכת מנסה להחזיר לך תמיד הודעה על כל קריאה באופן סינכרוני ויצירת מצב ששתי הודעות נשלחות ומקבלים רק תשובה אחת מאוד בעיתי , אינני יודע אם אפשרי בכלל.
אנו כתבנו Custom channel משלנו והכנסנו אותו ל channel stack .

ה Channel הכיל את הלוגיקה כלומר את המימוש של ה Bridge שמתאם בין ההודעות , הchannel הינו statefull הוא זכר את כל המידע מהקריאות הקודמות שדרושות לצורך התאום .
הפיתרון מכיל את ה class ים הבאים :

LogicOrcBinding
הcustom binding שמכיל את ה binding elements שלנו .

הגדרנו רק 2 elements כאשר תשתיות ה wcf מוסיפות את החסר בהתאם לקונפיגורציה אם זה security , relaibale session וכד
התשתית מוסיפה את שאר ה elements במקומות הנכונים כך שהצפנה לדוגמה תתבצע אחרי הטיפול שלנו .
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
LogicOrcBindingElement , LogicOrcChannelFactory , LogicOrcChannelListener
מכילים את ה בוילרפארט קוד בד"כ לא נוגעים או משנים אותם. תפקידם לעטוף את היצירה של ה channel שלנו .
כאשר יש פרמטרים שצריך להעביר מה Binding עצמו ,צריך לדאוג להעביר דרכם .
LogicOrcBodyWriter
class עזר פשוט שתפקדו לעזור בלכתוב את ה פרמטרים של הAction בMessage
הClass מכיל event בשם writeBodyCallback שנקרא אוטומטית על ידי המערכת לצורך עדכון הפרמטרים .

LogicOrcChannel
זהו לב המערכת

בCtor אנו מקבלים Reference ל Channel הבא בstack

אנו נשתמש בReference הזה לשני דברים :
1.להפנות מימוש methods למימוש ב Channel הבאה בstack ב methods שאין לנו צורך לממשן
לדוגמא:

2.הפניית הMessage אחרי הטיפול לChannel הבאה ב stack
הmethods שמטפלות בשליחת הנתונים ל channel הבאה בstack הינן :
public void Send(Message message, TimeSpan timeout) ,
public IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
כאן משתלבת הלוגיקה העסקית של הBridge בשליחה של הודעות לServer ,ניתן לא להעביר את הMessage ל channel הבא ובכך לחסום את ההודעה ניתן להעביר הודעה אחרת או מספר הודעות אחרות ,כמו כן ניתן להעביר את הטיפול ל thread אחר ובזמן שונה להעביר הודעה ל Channel הבא ב Stack .

דוגמא למימוש
פונקצית עזר לבניית Message חדש בהסתמך על העתקת ה Header של ה message שהתקבל מבChannel הקודם ושינוי ה Action

הפונקציה הבאה מקבלת פקודה אחת ומפצלת אותה ל שניים

הMethods שמטפלות בקריאת הנתונים בצורה סינכרונית או אסינכרונית בהתאמה הינן :
public bool TryReceive(TimeSpan timeout, out Message message)
והצמד
public
IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state)
public bool EndTryReceive(IAsyncResult result, out Message message)
אחרי ששלחנו שתי הודעות Call1 וCall2 ל Client אמו מקבלים גם שתי תשובות אותן אנו צריכים לאחד לתשובה אחת .את הפעולה הזו אנו עושים ב EndTryReceive

Exception
בזמן הפיתוח אם מבצעים שגיאה בקריאה של ה Contract לדוגמא קוראים ל Action שאיננו נמצא ב contrat של ה Server המערכת נותנת שגיאה מפורטת :
{"The message with Action 'http://tempuri.org/ILogicOrcSample/CallWithWrongAction' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None)."}
טעיות בשמות של ה פרמטרים בד"כ יגרמו ל להעברת null בפרמטרים של ה method
בדיקות
שתי בדיקות נעשו לצורך ולידציה של הפתרון :
1.בדיקת memory leak שהמערכת לא משאירה אצלה זבל במקרה ונחסמת פקודה מלהגיע ליעדה וכד.על מנת לבצע את הבדיקות עם התוכנית דוגמא יש להוריד את כל ה חלק של ה Diagnostic מה Configuration files , לבצע את הקריאות הנדרשות בloop ומידי פעם לקרא ל GC.Collect
2.בדיקת ביצועים ש ה Bridge Channel לא פוגע בביצועים כתוצאה מביצוע הלוגיקה שלו
בבדיקה שבצעתי לא נראה כל דליפת זיכרון אך אני ממליץ בחום לכל מי שחושב על הפתרון לבדוק גם את נושא הזיכרון , יציבות , ביצועים של הקונספט האם הם מתאימים לדרישותיו.
קישור לקוד דוגמא:
קוד דוגמא מאוד בסיסי נמצא כאן .
הקוד מבוסס על דוגמא של מיקרוסופט בשם Chunking channel לדוגמא הוכנסו רק הדברים האלמנטריים ביותר לצורך הדגמת הקונספט .
על מנת להשתמש בה יש כמוסן להוסיף לה את כל הקוד הסטנדרטי כגון טיפול ב Timeouts סגירת Channels ללא exceptions וכד.