Upravo sam izbrisao stotine redaka koda koje sam jučer napisao i zamijenio ih s 32 retka novog koda. Ovo je bilo za značajku za TheOpenPresenter , koja se koristi za označavanje reproducira li se zvuk.
Svako toliko radio bih na funkciji koja se čini prilično jednostavnom za implementaciju. U ovom slučaju, samo sam trebao prikazati ovu ikonu kada se reproducira zvuk.
Dovoljno jednostavno. Svaki od njih je scena koja sadrži više dodataka. Svaki dodatak ima vlastito svojstvo poput isPlaying
. Možemo spojiti vrijednosti između dodataka, a ako je zastavica istinita, možemo prikazati ikonu.
Glavno pitanje je kako pristupiti tim podacima. Vidite, mogli bismo izravno pristupiti podacima. Ali svaki dodatak može imati vlastitu shemu. Dok neki dodaci mogu imati jednostavno svojstvo isPlaying , neki drugi možda trebaju nešto kompliciranije za predstavljanje statusa reprodukcije.
Jednostavno, zašto ne dopustiti dodatku da registrira povratni poziv/funkciju koja vraća stanje?
Ovo je isti obrazac koji TheOpenPresenter koristi za mnoge svoje dodatke. I kad smo već kod toga, možemo ga apstrahirati u SceneState objekt. Dakle, ako nam ikada zatreba neko drugo stanje, možemo ga dodati ovdje. Evo kako bi to moglo izgledati za dodatak:
// 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), } }, );
Primijetite da se gornjim kodom rukuje na poslužitelju. To je zato što se TheOpenPresenter sastoji od 3 odvojene komponente:
Daljinski - gdje se prikazuje ova zvučna oznaka
Renderer - reproducira zvuk
Server - povezuje to dvoje
U idealnom slučaju, ovo bismo trebali riješiti u sučelju (na daljinskom upravljaču) kako ne bismo dodatno opterećivali poslužitelj. Međutim, registracija ove funkcije može biti neuredna. Naše sučelje koristi arhitekturu mikro sučelja s web komponentama.
Crveno područje ispod je React shell. Zeleno područje se učitava putem web komponenti i njime upravlja svaki dodatak.
Primijetite da se ikona zvuka nalazi na lijevoj strani školjke. Kako ljusci pružiti funkciju koja nam je potrebna? Mogli bismo uključiti JS funkciju u paket web komponenti, ali to dugoročno zvuči kao nered.
Rukovanje ovim na poslužitelju čini se kao ispravan način za to.
Kad smo to odlučili, vrijeme je za provedbu. Treba učiniti nekoliko stvari:
Neću vas zamarati detaljima pa evo pregleda. API nije bio sasvim jednostavan jer naši podaci mogu biti prilično zbunjujući. Ukratko: scena može imati više dodataka. Može postojati više renderera, od kojih svaki gleda scenu na drugačiji način. Dakle, dodatak može imati više renderera koji ga prikazuju na različite načine. Ali uz malo manipulacije podacima, problem je riješen.
Konzumiranje i ažuriranje korisničkog sučelja
Konzumiranje vrijednosti bilo je jednostavno. Razmišljao sam o korištenju Yjs-ovog protokola svjesnosti za pružanje podataka budući da je u stvarnom vremenu i okvir je već postavljen. Ovako se čuva država. Međutim, uključivanje ovih podataka s poslužitelja je vlastiti problem. Pa sam umjesto toga odlučio koristiti GraphQL - protokol koji koristimo za sve ostalo na platformi.
Dakle, sve što trebamo učiniti je nazvati krajnju točku, poslušati je koristeći pretplatu na GraphQL i prikazati ikonu po potrebi. Gotovo.
Pružanje ovih podataka sučelju
Srećom, koristimo Postgraphile koji proširenje GraphQL sheme čini prilično jednostavnim. Također možemo napraviti pretplatu jednostavnim dodavanjem @pgSubscription
u GraphQL shemu. Zatim će promatrati temu i ažurirati vrijednost kad god pozovemo pg_notify
za tu temu. Na primjer:
await pgPool.query( `select pg_notify('graphql:sceneState:${id}','{}');`, [], );
Manipuliranje podacima bilo je neugodno, ali samo malo strpljenja i gotovo je!
Posljednji dio slagalice poziva pg_notify
kada treba.
U tu svrhu možemo dodati slušatelja u stanje (Yjs) i pozvati obavijest kad god se nešto promijeni:
state.observeDeep(async () => { // Call pg_notify here });
Jedino što preostaje je poboljšati performanse. Trenutačno se funkcija poziva za svaku malu promjenu, također je ažurirana na sučelje. Možemo izračunati rezultirajuće stanje i usporediti ako se nešto promijeni prije nego što pokrenemo ažuriranje.
Sada ovo rješenje sigurno funkcionira. Ali mrzio sam što smo slušali svaku promjenu. To je nepotrebno i nisam siguran kako će se izvedba skalirati. Postoji li bolje rješenje?
Pa sam se na trenutak odmaknuo i došla je ideja: kako bi bilo da prijeđemo na osnove i upotrijebimo podatke iz Yjs-a?
Problem je bio u tome što svaki dodatak može koristiti različite načine za označavanje statusa reprodukcije. Stoga nam je trebao način da znamo kako sami izračunati rezultirajuće stanje. Ali umjesto da dopustite korisniku da proslijedi funkciju, zašto ne rezervirati svojstvo koje može koristiti da to označi?
Umjesto prosljeđivanja funkcije za izračunavanje stanja, svaki dodatak može postaviti rezervirano stanje izravno uz svoje postojeće podatke sa svojstvima kao što je __audioIsPlaying
. Ovu vrijednost mogu koristiti izravno ili je mogu sinkronizirati sa svojim postojećim svojstvima na sljedeći način:
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")); }, ); };
Nova metoda je briljantna. Nema dodatnog slušača, nema dodatnog API-ja, samo jednostavno rezervirano svojstvo.
trošak? Pa već sam napisao 95% prve implementacije 🫣
“Bit će šteta ovo izbrisati kad sam tako dugo radio na tome. Sve ostalo je savršeno osim ove jedne stvari!” - Moj um
Ovo mi nije prvi put. Ni drugi ni treći. Ovaj put radilo se o samo nekoliko sati rada. Što je dulje potrebno za implementaciju, to je teže otpustiti. Ali ako se ne bismo trebali vezati za poslužitelje, ne bismo trebali biti vezani ni za kod koji pišemo.
Očito je da je druga implementacija bolja. Brži je, manje pokretnih dijelova, manje API površine i manje koda za održavanje. Prva implementacija dodala je 289 novih redaka, dok je druga implementacija dodala samo 32 nova retka.
Što onda treba naučiti?
Pa, možda prvo pronađite najjednostavnije rješenje. Ali ponekad ne dođemo do najboljeg rješenja samo razmišljajući o njemu. Ako je to slučaj, nemojte voljeti svoj kod i nemojte se bojati baciti ga . I možda napišite post na blogu tako da izvučete nešto iz toga!
Ako ste čitali dovde, možda biste trebali isprobati TheOpenPresenter ! To je prezentacijski sustav otvorenog koda koji vam omogućuje daljinsko upravljanje bilo kojim zaslonom.
Prikažite dijaprojekcije, reproducirajte videozapise, koristite ih kao nadzorne ploče i još mnogo toga. Softver je još uvijek u ranoj fazi razvoja, kao što možete vidjeti iz ovog posta, ali je dovoljno stabilan za redovitu upotrebu. Osobno ga koristim za svoje sastanke svaki tjedan.
Bilo kakva pitanja, pitajte. Ili slobodno prijavite probleme u Github repo .