Moonlight Animate: dołączanie do sesji współpracy bez pustego płótna (late-join v2)
Współpraca w czasie rzeczywistym ma jeden nieoczywisty, trudny moment: co widzi osoba, która dołącza w środku sesji? Wcześniejsza wersja radziła sobie z tym dobrze dla scen animacji — nowy uczestnik dostawał zapisaną scenę i „dogrywkę" wszystkich operacji z dziennika serwera, więc widział aktualny stan. Ale dla sesji na płótnie rastrowym (malowanie) była dziura: serwer nie przechowywał żadnego zrzutu pikseli, więc późny uczestnik startował od… pustego obrazu. Domykamy to w tej fazie.
Problem
Podczas sesji współpracy operacje (pociągnięcia pędzla, zmiany struktury warstw, geometria dokumentu) są sekwencjonowane i journalowane na serwerze — to pozwala odtworzyć je u kogoś, kto dołączył później. Ale odtwarzanie wszystkiego od zera ma dwie wady:
- Płótno rastrowe nie miało punktu startowego. Bez bazowego obrazu odtwarzanie samych „różnic" nie miało na czym usiąść — joiner widział pustkę.
- Długie sesje = wolne dołączanie. Im dłużej trwa sesja, tym dłuższy dziennik do odtworzenia.
Rozwiązanie: baza od gospodarza + kompakcja
W sesji rastrowej gospodarz (właściciel sesji) co kilkadziesiąt sekund utrwala na serwer pełną bazę dokumentu — kompletny zrzut projektu w natywnym formacie. Wraz z nią serwer zapamiętuje znacznik mówiący, do którego momentu historii ta baza sięga.
Dzięki temu osoba dołączająca:
- pobiera bazę i ustawia nią aktualne piksele (zamiast pustki),
- a potem dogrywa tylko ogon operacji — te, które pojawiły się po utrwaleniu bazy.
Po utrwaleniu bazy serwer kompaktuje dziennik (usuwa operacje już zawarte w bazie), więc kolejni uczestnicy dołączają się szybko nawet w bardzo długich sesjach.
Najtrudniejsze: dokładność do jednej operacji
Cała poprawność stoi na jednym warunku: baza musi odzwierciedlać dokładnie te operacje, których znacznik dotyczy — ani jedną mniej, ani jedną więcej. Inaczej dołączający albo zgubiłby zmianę, albo zastosowałby ją podwójnie (np. warstwa dodana dwa razy).
Rozwiązaliśmy to kilkoma sprzężonymi mechanizmami:
- Gospodarz utrwala bazę tylko, gdy nie ma własnej operacji „w locie". Wtedy odczyt znacznika i serializacja dokumentu odbywają się w jednym, nieprzerywalnym kroku, więc baza odpowiada dokładnie znanemu numerowi.
- Baza jest wersjonowana numerem operacji, a serwer zawsze wydaje tę wersję, która pasuje do zatwierdzonego znacznika — i dokłada ten numer w nagłówku odpowiedzi.
- Dołączający przykleja swój punkt startowy odtwarzania do numeru z tego nagłówka, a nie do wartości sprzed pobrania. Dzięki temu „baza" i „od którego miejsca dogrywać" to zawsze ten sam numer — nawet jeśli gospodarz zaktualizował bazę dokładnie w trakcie czyjegoś dołączania.
- Okno bezpieczeństwa przy kompakcji: najświeższe operacje zostają w dzienniku jeszcze przez chwilę, żeby ktoś dołączający w trakcie nic nie zgubił.
Te scenariusze wyłapał wewnętrzny adwersaryjny przegląd — race przy równoległym zaawansowaniu bazy potrafił podwoić operacje, dlatego trafił na listę napraw przed wydaniem.
Przy okazji: kafelkowanie w Texture Ripperze
Domknęliśmy też drobiazg w narzędziu Texture Ripper (wyrywanie tekstur ze zdjęć z korekcją perspektywy). Przełącznik „kafelkowalna" na pojedynczym ripperze respektuje teraz wybraną w panelu metodę leczenia szwu (Content-Aware, Blend, Mirror, Scattered) i szerokość pasa szwu — wcześniej przełącznik ignorował te ustawienia i zawsze używał jednego trybu. Wybór zapisuje się w pliku projektu i synchronizuje podczas współpracy.
Co to daje
- Dołączenie do trwającej sesji malowania pokazuje aktualny obraz, nie pustkę.
- Długie sesje dołączają się szybko, bo nie trzeba odtwarzać całej historii.
- Kafelkowalne tekstury z Texture Rippera robią dokładnie to, co ustawisz w panelu.
Sesje scen animacji działają jak dotąd — ta zmiana dotyczy płótna rastrowego i porządkuje fundament pod jeszcze płynniejszą współpracę.