Решили: третья реализация шва транспорта — NgoTransport поверх Netcode for GameObjects
(именованные сообщения, одно имя на все каналы), плюс CoopSession: поднять хост, войти гостем,
рукопожатие с отпечатком контента, «хост ушёл — конец сессии». В сцене CoreScene появился объект
[Net] с NetworkManager и UnityTransport.
Почему так:
- Одно имя сообщения на все каналы. NGO умеет разводить потоки по строковому имени, но канал у нас уже живёт в первом байте конверта. Второй способ разводить то же самое означал бы двух владельцев одного правила и расхождение при добавлении канала.
- Очередь на приёме, а не прямой проброс. NGO поднимает колбэк внутри своего апдейта, а наш
INetTransportобещает доставку только вPoll. Обещание держит воспроизводимость тестов, где два узла живут в одном процессе, поэтому качает транспорт отдельныйNetPump. - Себе не шлём.
SendNamedMessageToAllу хоста включает его собственного клиента; наш контракт — «всем, кроме себя». Иначе хост получал бы эхо каждого своего чанка ленты. EnableSceneManagement = false. Сцены грузит нашSceneLoader, мир persist, бой едет лентой — синхронизация сцен NGO нам не нужна и мешала бы.
Грабли — две, обе дорогие.
NetworkManager.MaximumFragmentedMessageSize бросает NullReferenceException, пока сеть не
поднята. Свойство спрашивает менеджер сообщений, а тот создаётся вместе с сессией. Поймано в
редакторе при настройке [Net]: тот же вызов из нашего MaxReliableMessageBytes уронил бы стример
в соло-игре, где сеть не запущена вовсе. Значение теперь снимается один раз при старте, до старта
отдаётся MTU-безопасный минимум.
Предел надёжного сообщения у транспортов разный, и наш потолок чанка в него не влезает. У Steam
это 512 КБ, у UTP — MaximumFragmentedMessageSize (заметно меньше), а TapeChunkFormat.MaxChunkBytes
— 64 КБ. Сверх предела сообщение не уезжает вовсе и молча. Поэтому стример спрашивает предел у
транспорта и, если чанк не влез, делит диапазон тиков пополам и пишет заново.
Деление потребовало TapeChunkWriter.DiscardLast: номер чанка тратится при записи, и без отката у
приёмника осталась бы дыра в нумерации, которую он просил бы повторить до конца боя. То есть откат
— не удобство, а условие того, что деление вообще работает.
Владелец правды: Net/Transport/NgoTransport.cs, Net/Session/CoopSession.cs, Net/NetPump.cs,
Net/Tape/TapeStreamer.cs (деление под предел); тест
TapeDeliveryTests.ChunkTooBigForTheTransport_IsSplit_NotDropped держит и деление, и непрерывность
нумерации после него.