פשוט מחקתי מאות שורות קוד שכתבתי אתמול והחלפתי אותן ב-32 שורות קוד חדש. זה היה עבור תכונה עבור TheOpenPresenter , המשמשת לציון אם שמע מתנגן.
מדי כמה זמן, הייתי עובד על פונקציונליות שנראית די פשוטה ליישום. במקרה זה, אני רק צריך להציג את הסמל הזה כאשר אודיו מתנגן.
פשוט מספיק. כל אחד מאלה הוא סצנה המכילה מספר תוספים. לכל תוסף יש מאפיין משלו כמו isPlaying
. נוכל למזג את הערכים בין התוספים, ואם הדגל נכון, נוכל להציג את הסמל.
הבעיה העיקרית היא איך לגשת לנתונים האלה. תראה, נוכל לגשת ישירות לנתונים. אבל לכל תוסף יכול להיות סכימה משלו. בעוד שלחלק מהתוספים עשוי להיות מאפיין isPlaying פשוט, חלק אחר עשוי להזדקק למשהו מסובך יותר כדי לייצג את סטטוס המשחק שלו.
פשוט, למה לא לאפשר לפלאגין לרשום התקשרות חוזרת/פונקציה שמחזירה את המצב?
זהו אותו דפוס שבו משתמש TheOpenPresenter עבור רבים מהפלאגינים שלו. ובזמן שאנחנו בזה, אנחנו יכולים להפשט את זה לאובייקט SceneState . אז אם אי פעם נצטרך מדינה אחרת כלשהי, נוכל להוסיף אותה כאן. כך זה עשוי להיראות עבור הפלאגין:
// The pattern we use for plugins serverPluginApi.onPluginDataCreated(pluginName, onPluginDataCreated); serverPluginApi.onPluginDataLoaded(pluginName, onPluginDataLoaded); serverPluginApi.registerRemoteViewWebComponent( pluginName, remoteWebComponentTag, ); // Example of how the new API might look like serverPluginApi.registerSceneState( pluginName, (_, rendererData) => { return { audioIsPlaying: !!rendererData.find((x) => x.isPlaying), } }, );
שימו לב שהקוד למעלה מטופל בשרת. הסיבה לכך היא ש-TheOpenPresenter מורכב מ-3 רכיבים נפרדים:
השלט הרחוק - היכן מוצג חיווי האודיו הזה
The Renderer - מנגן את האודיו
השרת - מחבר בין השניים
באופן אידיאלי, עלינו לטפל בזה ב-frontend (מרוחק) כדי שלא נוסיף עומס נוסף לשרת. עם זאת, רישום פונקציה זו עשוי להיות מבולגן. החזית שלנו משתמשת בארכיטקטורת מיקרו-חזית העמוסה ברכיבי אינטרנט.
האזור האדום למטה הוא מעטפת React. השטח הירוק נטען באמצעות רכיבי אינטרנט ומנוהל על ידי כל תוסף.
שימו לב שסמל השמע ממוקם בצד שמאל של המעטפת. כיצד אנו מספקים את הפונקציה שאנו צריכים למעטפת? אנחנו יכולים לכלול פונקציית JS בחבילת רכיבי האינטרנט אבל זה נשמע כמו בלגן בטווח הארוך.
טיפול זה בשרת נראה כמו הדרך הנכונה לעשות זאת.
לאחר שהוחלט, הגיע הזמן ליישום. יש כמה דברים לעשות:
אני לא אשעמם אותך בפרטים אז הנה סקירה כללית. ה-API לא היה ממש פשוט מכיוון שהנתונים שלנו יכולים להיות די מבלבלים. בקיצור: סצנה יכולה לכלול מספר תוספים. ויכולים להיות מעבדים מרובים, כל אחד צופה בסצנה בצורה שונה. אז לפלאגין יכולים להיות מעבדים מרובים שמציגים אותו בדרכים שונות. אבל עם קצת מניפולציה של נתונים, הבעיה נפתרה.
צריכת ועדכון ממשק המשתמש
צריכת הערך הייתה פשוטה. שקלתי להשתמש בפרוטוקול המודעות של Yjs כדי לספק את הנתונים מכיוון שהם בזמן אמת והמסגרת כבר קיימת. כך נשמרת המדינה. עם זאת, הכללת נתונים אלה מהשרת היא בעיה משלו. אז החלטתי להשתמש במקום זאת ב-GraphQL - הפרוטוקול שבו אנו משתמשים עבור כל השאר בפלטפורמה.
אז כל מה שאנחנו צריכים לעשות הוא להתקשר לנקודת הקצה, להאזין לה באמצעות המנוי של GraphQL, ולהראות את הסמל לפי הצורך. נַעֲשָׂה.
מתן נתונים אלה ל-frontend
למרבה המזל, אנו משתמשים ב-Postgraphile מה שהופך את הרחבת סכימת GraphQL לפשוטה למדי. אנחנו יכולים גם להפוך אותו למנוי פשוט על ידי הוספת @pgSubscription
לסכימת GraphQL. לאחר מכן הוא יצפה בנושא ויעדכן את הערך בכל פעם שנתקשר ל- pg_notify
בנושא זה. לְדוּגמָה:
await pgPool.query( `select pg_notify('graphql:sceneState:${id}','{}');`, [], );
מניפולציה של הנתונים הייתה מעצבנת אבל רק קצת סבלנות וזה נעשה!
החלק האחרון בפאזל הוא קריאה pg_notify
כשצריך.
לשם כך, אנו יכולים להוסיף מאזין למדינה (Yjs) ולהתקשר להודעה בכל פעם שמשהו משתנה:
state.observeDeep(async () => { // Call pg_notify here });
הדבר היחיד שנותר לעשות הוא שיפורי ביצועים. כרגע, הפונקציה נקראת עבור כל שינוי קטן, היא גם מתעדכנת ל-frontend. אנו יכולים לחשב את המצב המתקבל ולהשוות אם משהו משתנה לפני דחיפה של העדכון.
עכשיו הפתרון הזה בהחלט עובד. אבל שנאתי שאנחנו מקשיבים לכל שינוי. זה מיותר ואני לא בטוח איך הביצועים יסתכמו. האם יש פתרון טוב יותר?
אז צעדתי אחורה לשנייה והגיע רעיון: מה דעתך שנלך ליסודות ונשתמש בנתונים מ-Yjs?
הבעיה הייתה שכל תוסף עשוי להשתמש בדרכים שונות כדי לציין סטטוס הפעלה. אז היינו צריכים דרך לדעת איך לחשב את המצב שנוצר בעצמנו. אבל במקום לאפשר למשתמש להעביר פונקציה, למה לא לשמור מאפיין שהוא יכול להשתמש בו כדי לציין זאת?
במקום להעביר פונקציה לחישוב המצב, כל תוסף יכול להגדיר את המצב השמור ישירות לצד הנתונים הקיימים שלו עם מאפיינים כמו __audioIsPlaying
. הם יכולים להשתמש בערך זה ישירות, או שהם יכולים לשמור אותו מסונכרן עם המאפיינים הקיימים שלהם, כך:
const onRendererDataLoaded = ( rendererData, ) => { watchYjs( // Watch the isPlaying property (x) => x.isPlaying, () => { // And if it changes, sync the __audioIsPlaying property rendererData.set("__audioIsPlaying", rendererData.get("isPlaying")); }, ); };
השיטה החדשה מבריקה. אין מאזין נוסף, אין API נוסף, רק נכס שמור פשוט.
העלות? ובכן, כבר כתבתי 95% מהיישום הראשון 🫣
"זה יהיה כל כך חבל למחוק את זה כשעבדתי על זה כל כך הרבה זמן. כל השאר מושלם מלבד הדבר האחד הזה!" - המוח שלי
זו לא הפעם הראשונה שלי. גם לא שני או שלישי. הפעם זה היה רק כמה שעות של עבודה. ככל שלוקח יותר זמן ליישם, כך קשה יותר להרפות. אבל אם אנחנו לא צריכים להיות מחוברים לשרתים, אנחנו גם לא צריכים להיות מחוברים לקוד שאנחנו כותבים.
ברור שהיישום השני טוב יותר. זה מהיר יותר, פחות חלקים נעים, פחות משטח API ופחות קוד לתחזוקה. היישום הראשון הוסיף 289 שורות חדשות ואילו היישום השני הוסיף רק 32 שורות חדשות.
אז מה הלקח שצריך ללמוד?
ובכן, אולי תמצא קודם את הפתרון הפשוט ביותר. אבל לפעמים אנחנו לא מגיעים לפתרון הטוב ביותר רק על ידי חשיבה על זה. אם זה המקרה, אל תאהב את הקוד שלך ואל תפחד לזרוק אותו . ואולי תכתוב פוסט בבלוג כדי שתפיק מזה משהו!
אם קראת עד כאן, אולי תרצה לנסות את TheOpenPresenter ! זוהי מערכת מצגות בקוד פתוח המאפשרת לך לשלוט בכל אחד מהמסכים שלך מרחוק.
הצג מצגות, הפעל סרטונים, השתמש כלוחות מחוונים ועוד רבים אחרים. התוכנה עדיין בשלב מוקדם מאוד בפיתוח כפי שניתן לראות מפוסט זה, אך היא יציבה מספיק לשימוש קבוע. אני אישית משתמש בו למפגשים שלי מדי שבוע.
כל שאלה, תשאל. או אל תהסס לדווח על בעיות בריפו של Github .