Задача
Есть приложение, клиент-сервер. Клиент — Unity3d сервер PhotonServer. Есть модель, которая и на клиенте и на сервере должна быть эквивалентной. Требуется синхронизировать состояние модели и, возможно, дополнительные классы.
Решение
Protobuf
Самое логичное решение — это использовать бинарный протокол. В этом явный фаворит — ptotobuf (использовал proto-net 668). Он не поддерживает веб-сборку, но это допустимая жертва. Разметил требуемые классы. Проверяю. Все работает, небольшой размер и быстрый в работе. Шикарно. Но!
В один прекрасный момент Protobuf выплюнул екзепшен, мол, такой класс не найден. Как это?
Баг подробно с примером кода.
Начал различными способами решать эту проблему. Есть вариант скармливать Protobuf типы. Что уже не хорошо. Можно допустить достаточно много ошибок или забыть указать тот или иной тип. Более того, Protobuf не поддерживает многомерные массивы.
Как ни прискорбно, но Protobuf придется в сторону. К слову, однажды пытался использовать Protobuf в связке php и Unity. Со стороны php реализация Protobuf оказалась достаточно баганутой. В итоге в php и Unity использовал json. Это сработало, потому что между php и Unity ходили довольно-таки простые структуры данных.
Message pack
Сещуствует еще один примечательный сериализатор. Есть реализации на огромное количество языков. Замечательно. Решил попробовать. Примитивный тип сериализовал нормально. Размер 18 байт против моего 41 байта, против 19 байтов protobuf и против 44 байтов json. Отличный результат. В чем же хитрость? На официальном сайте есть пример, как он на самом деле все пакует. Вот ссылка.
[Serializable, ProtoContract()]
public class TTT
{
[TDataMember, ProtoMember(1)]
public string s = "compact";
[TDataMember, ProtoMember(2)]
public bool f = true;
[TDataMember, ProtoMember(3)]
public string s2 = "schema";
[TDataMember, ProtoMember(4)]
public short i = 0;
}
Но сложный пример, который будет, далее не осилил message pack и protobuf.
Примеры ошибок.
PlatformNotSupportedException: On-the-fly enum serializer generation is not supported in Unity iOS. Use pre-generated serializer instead.
MsgPack.Serialization.ReflectionSerializers.ReflectionSerializerHelper.CreateReflectionEnuMessagePackSerializer[State] (MsgPack.Serialization.SerializationContext context)
MsgPack.Serialization.MessagePackSerializer.CreateReflectionInternal[State] (MsgPack.Serialization.SerializationContext context)
MsgPack.Serialization.MessagePackSerializer.CreateReflectionInternal (MsgPack.Serialization.SerializationContext context, System.Type targetType)
MsgPack.Serialization.SerializationContext.GetSerializer (System.Type targetType, System.Object providerParameter)
MsgPack.Serialization.ReflectionSerializers.ReflectionSerializerHelper.GetMetadata (MsgPack.Serialization.SerializationContext context, System.Type targetType, System.Func`2[]& getters, System.Action`2[]& setters, System.Reflection.MemberInfo[]& memberInfos, MsgPack.Serialization.DataMemberContract[]& contracts, MsgPack.Serialization.IMessagePackSerializer[]& serializers)
MsgPack.Serialization.ReflectionSerializers.ReflectionObjectMessagePackSerializer`1[TestS+TestC]..ctor (MsgPack.Serialization.SerializationContext context)
MsgPack.Serialization.MessagePackSerializer.CreateReflectionInternal[TestC] (MsgPack.Serialization.SerializationContext context)
MsgPack.Serialization.SerializationContext.GetSerializer[TestC] (System.Object providerParameter)
MsgPack.Serialization.SerializationContext.GetSerializer[TestC] ()
/// [,] string
ArgumentException: 'System.String[,]' is not compatible for 'System.String[]'.
Parameter name: objectTree
MsgPack.Serialization.MessagePackSerializer`1[System.String[]].MsgPack.Serialization.IMessagePackSerializer.PackTo (MsgPack.Packer packer, System.Object objectTree)
MsgPack.Serialization.ReflectionSerializers.ReflectionObjectMessagePackSerializer`1[TestS+TestC].PackMemberValue (MsgPack.Packer packer, .TestC objectTree, Int32 index)
MsgPack.Serialization.ReflectionSerializers.ReflectionObjectMessagePackSerializer`1[TestS+TestC].PackToCore (MsgPack.Packer packer, .TestC objectTree)
MsgPack.Serialization.MessagePackSerializer`1[TestS+TestC].PackTo (MsgPack.Packer packer, .TestC objectTree)
MsgPack.Serialization.MessagePackSerializer`1[TestS+TestC].Pack (System.IO.Stream stream, .TestC objectTree)
/// etc
SerializationException: Non generic collection may contain only MessagePackObject type.
MsgPack.Serialization.DefaultSerializers.NonGenericEnumerableSerializerBase`1[T].PackToCore (MsgPack.Packer packer, .T objectTree)
MsgPack.Serialization.MessagePackSerializer`1[T].MsgPack.Serialization.IMessagePackSerializer.PackTo (MsgPack.Packer packer, System.Object objectTree)
MsgPack.Serialization.ReflectionSerializers.ReflectionCollectionSerializer`1[System.Collections.ArrayList].PackToCore (MsgPack.Packer packer, System.Collections.ArrayList objectTree)
MsgPack.Serialization.MessagePackSerializer`1[System.Collections.ArrayList].MsgPack.Serialization.IMessagePackSerializer.PackTo (MsgPack.Packer packer, System.Object objectTree)
MsgPack.Serialization.ReflectionSerializers.ReflectionObjectMessagePackSerializer`1[TestS+TestC].PackMemberValue (MsgPack.Packer packer, .TestC objectTree, Int32 index)
MsgPack.Serialization.ReflectionSerializers.ReflectionObjectMessagePackSerializer`1[TestS+TestC].PackToCore (MsgPack.Packer packer, .TestC objectTree)
MsgPack.Serialization.MessagePackSerializer`1[TestS+TestC].PackTo (MsgPack.Packer packer, .TestC objectTree)
MsgPack.Serialization.MessagePackSerializer`1[TestS+TestC].Pack (System.IO.Stream stream, .TestC objectTree)
NotSupportedException: Multi-dimension arrays are supported
ProtoBuf.Meta.MetaType.ResolveListTypes (ProtoBuf.Meta.TypeModel model, System.Type type, System.Type& itemType, System.Type& defaultType)
ProtoBuf.Meta.MetaType.ApplyDefaultBehaviour (Boolean isEnum, ProtoBuf.ProtoMemberAttribute normalizedAttribute)
ProtoBuf.Meta.MetaType.ApplyDefaultBehaviour ()
ProtoBuf.Meta.RuntimeTypeModel.FindOrAddAuto (System.Type type, Boolean demand, Boolean addWithContractOnly, Boolean addEvenIfAutoDisabled)
ProtoBuf.Meta.RuntimeTypeModel.GetKey (System.Type type, Boolean demand, Boolean getBaseKey)
Json
Итак, Protobuf не подходит. Что использовать? Json? Почему бы и нет. Тут вторая проблема: джейсон не умеет сериализовать поля типа интерфейс и абстрактные классы. Не беда, воспользовавшись гуглом нашел, как «научить» его делать это. В итоговом файле появились данные о типе поля и его данные (с указанием сборки, это важно; почему — написано далее). Но при десериализации это поле почему-то нул. Снова гуглю. Ведь если научил сериализовать, значит можно и десериализовать. Получается тот же костыль, что и с Protobuf. Такой вариант не подходит. Использовал сборку JSON .NET For Unity, который есть в ассетмаркете.
Итог: Json хорош для не сложных структур. Но когда есть поля типа абстрактный класс или интерфейс, с ним возникают проблемы.
XML
В любой вариации xml — достаточно громоздкий. Поэтому решил не рассматривать. Хоть и часть проекта на xml. Например, система локализации.
BinaryFormatter
Решил обратится к стандартным средствам. Разметил код, сериализуем. Success! Большой объем файла, правда, не есть хорошо. Не беда, пройдемся еще и компрессией. Использовал LZMA. Выиграл немного в размере, но проиграл по скорости работы. Допустимая жертва. Теперь сборки. Барабанная дробь. Веб не поддерживается, беда…
Теперь устроим обмен между клиентом и сервером. И… Очередной FAIL. Дело в том, что сборки у классов разные, хоть классы одни и те же. В юнити своя сборка на фотоне своя. Можно решить через костыльный способ. Забиндить сборки и вручную их переименовать, но сборка попадает в бинарный файл. Зачем она там нужна?
Решил что к этому способу вернусь, просмотрел еще парочку сериализаторов. Один из них Шарп сериализатор. Смог сериализовать поля типа интерфейс, но тоже прописывает сборку и не поддерживается в вебе. Тогда я решил сначала сформировать требования к сериализатору.
TTT c = new TTT();
TSerizalization serizalization = new TSerizalization();
bytes = serizalization.Serizalize(c, true);
System.IO.File.WriteAllBytes("d:\\s.dat", bytes);
Debug.LogError("T complete " + bytes.Length );
json = JsonConvert.SerializeObject(c);
System.IO.File.WriteAllText("d:\\s.json", json);
Debug.LogError("J complete " + json.Length);
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formater = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
formater.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full;
System.IO.MemoryStream mstream = new System.IO.MemoryStream();
formater.Serialize(mstream, c);
Debug.LogError("B complete " + mstream.ToArray().Length);
System.IO.File.WriteAllBytes("d:\\s2.dat", mstream.ToArray());
mstream = new System.IO.MemoryStream();
var serializer = MsgPack.Serialization.SerializationContext.Default.GetSerializer<TTT>();
serializer.Pack(mstream, c);
System.IO.File.WriteAllBytes("d:\\s3.dat", mstream.ToArray());
Debug.LogError("M complete " + mstream.ToArray().Length);
mstream = new System.IO.MemoryStream();
ProtoBuf.Serializer.Serialize<TTT>(mstream, c);
System.IO.File.WriteAllBytes("d:\\s4.dat", mstream.ToArray());
Debug.LogError("P complete " + mstream.ToArray().Length);
Требования к сериализатору
Требуемый сериализатор должен уметь:
- Сериализовать в бинарный формат;
- Сериализовать в небольшой размер;
- Сериализовать классы используя конкретные сборки (при этом информация о сборке необязательно нужна в файле);
- Сериализовать пользовательские классы;
- Поддерживать массивы;
- Работать в веб версией юнити;
- Работать с фотоном.
Из тех сериализаторов, что я попробовал. Этим требованиям более менее соответствовал только Json и protobuf ранней версии.
Собственный сериализатор
Начал гуглить. Но безрезультатно. Что же делать?
Использовать стандартное решение — не очень хороший вариант. Большой размер. Проблемы с платформами.
Тогда я решил, под выше написанные требования написать собственный сериализатор. Почему бы и нет. Это оказалось гораздо эффективней, нежели в пустую разбирать и тестировать тот или иной сериализатор.
С чего лучше начать?
Как сохранить объекты и как их загрузить. В этом плане мне понравился подход protobuf-net 668. А именно — маркировать требуемые поля и свойства. Также маркировать и методы, которые будут вызваны до сериализации и после десериализации.
Карта
Для начала нужно сохранить карту. А именно — ключ и тип. Чтобы по этой карте можно было потом восстановить объект. Для стандартных типов значение < 0 и для пользовательских соответственно > 0. Размер ключа int16.
public class TMap
{
public Dictionary<Type, short> StandartTypes { get; protected set; }
public Dictionary<Type, short> DataBase { get; protected set; }
public Dictionary<short, Type> DataBaseTags { get; protected set; }
...
}
Коллекции вынес в отдельные теги что бы логически разделить.
TData множество объектов
Теперь нужно достать все поля и свойства. Запаковать их в свою структуру, чтобы присвоить им теги.
public class TData : TContainerBase
{
public object value;
public List<TData> childrens = new List<TData>();
...
}
Поскольку могут быть массивы, в контейнер добавляю информацию о мере массива.
public class TContainerBase
{
public short Tag { get; protected set; }
public int ArrayRank { get; protected set; }
public List<int> ArrayDimension { get; protected set; }
...
Я специально не делил на контейнер типа коллекция и контейнер типа объект. Все данные представлены в виде множества. Что и есть массив, если объект значит ранг и размерность в нулях.
Теперь нужен контейнер, в который будет восстановлен сам объект.
public class TContainer : TContainerBase
{
public int Size { get; protected set; }
public List<object> List { get; protected set; }
...
Далее. Со структурами данных завершено. Теперь нужно их заполнить.
Сначала карту типов и карту объекта. Для этого нужен класс, который все это объединит. Создаю дополнительно абстрактные классы для записи и считывания. Это на случай, если, например, понадобится добавить еще один формат. Тот же json или xml.
public abstract class TReaderBase
{
public abstract T Read<T>(byte[] bytes, Assembly assembly);
}
public abstract class TWriterBase
{
public abstract byte[] Write(TMap map, TData data);
}
Возможно, потом у них появятся дополнительные методы писать в поток и читать из потока. Но пока это не нужно. Теперь все это склеим в один класс.
public class TSerizalization
{
protected TMap map;
protected TWriterBase writer;
protected TReaderBase reader;
public TSerizalization()
{
writer = new TBinaryWriter();
reader = new TBinaryReader();
}
public virtual byte[] Serialize(object target, bool callBeforeSerializationMethods = false);
public virtual T Deserialize<T>(byte[] bytes, Assembly assembly, bool callAfterDeserializationMethods = false);
protected virtual TData Read(object obj)
}
Тест
Готово. Теперь перейдем к тестам. Осторожно много наборов данных.
public interface IClass
{
}
[System.Serializable ]
public class TestC : IClass
{
[To2dnd.TDataMember]
public int a = 10;
[To2dnd.TDataMember]
public int b = 12;
[To2dnd.TDataMember]
public string s= "Hello World";
[To2dnd.TDataMember]
public State state = State.Close;
[To2dnd.TDataMember]
public DateTime dt = new DateTime();
[To2dnd.TDataMember]
public Type type = typeof(IClass);
[To2dnd.TDataMember]
public string[,] arr = new string[,]
{
{"1111", "2222", "3333", "4444" },
{"aaaa", "bbbb", "cccc", "dddd" },
{"321", "32", "2qfs", "12f" }
};
[To2dnd.TDataMember]
public object classD = new TestC2();
[To2dnd.TDataMember]
public TestC1[] array1 = new TestC1[] { new TestC1(), new TestC2(), new TestC2() };
[To2dnd.TDataMember]
public ArrayList arr2 = new ArrayList( new string[]{ "list1", "list2" });
[To2dnd.TDataMember]
public List<string> list = new List<string>() { "list Item 1", "List Item 2" };
[To2dnd.TDataMember]
public Dictionary<string, int> dic = new Dictionary<string, int>()
{
{"one", 1},
{"two", 2},
{"three", 3},
{"four", 4}
};
[To2dnd.TDataMember]
public Hashtable ht = new Hashtable()
{
{"H one", 1},
{"H two", 2},
{"H three", 3},
{"H four", 4}
};
[To2dnd.TDataMember]
public SortedList<string, int> sl = new SortedList<string, int>()
{
{"S one", 1},
{"S two", 2},
{"S three", 3},
{"S four", 4}
};
[To2dnd.TDataMember]
public Dictionary<string, List<string>> dic3 = new Dictionary<string,List<string>>()
{
{">> 1", new List<string>(){"a1", "a2", "a3"} },
{">> 2", new List<string>(){"b1", "b2", "b3"} },
{">> 3", new List<string>(){"c1", "c2", "c3"} }
};
[To2dnd.TDataMember]
public List<List<string>> l = new List<List<string>>()
{
new List<string>(){"a1", "a2", "a3"},
new List<string>(){"b1", "b2", "b3"},
};
[ProtoMember(16)]
public Dictionary<string, Dictionary<string, string>> dic4 = new Dictionary<string, Dictionary<string, string>>()
{
{">> 1", new Dictionary<string, string>()
{
{ "a1", "a2"},
{ "a2", "a3"}
}
},
{">> 2", new Dictionary<string, string>()
{
{ "a1", "a2"},
{ "a2", "a3"}
}
}
};
[ProtoMember(17)]
public Dictionary<string, Dictionary<string, string>> Dic4 {get; protected set;}
[ProtoMember(18)]
public Dictionary<string, object> dic333 = new Dictionary<string, object>()
{
{":@", new List<string>(){"1", "2", "3"}},
{":@2", new TestC2()},
{":@222", "sff"}
};
public TestC()
{
Dic4 = new Dictionary<string,Dictionary<string,string>>()
{
{">> 1", new Dictionary<string, string>()
{
{ "a1", "a2"},
{ "a2", "a3"}
}
},
{">> 2", new Dictionary<string, string>()
{
{ "a1", "a2"},
{ "a2", "a3"}
}
}
};
}
}
[Serializable]
public class TestC1 : IClass
{
[To2dnd.TDataMember]
public float value1 = 10;
[To2dnd.TDataMember]
public float value2 = 12;
}
[Serializable ]
public class TestC2 : TestC1
{
[To2dnd.TDataMember]
public float a1 = 10;
[To2dnd.TDataMember]
public float b2 = 12;
[To2dnd.TDataMember]
public string str = "Class 1";
[To2dnd.TDataMember]
public State state = State.Close;
public TestC2()
{
}
[TAfterDeserialization]
public void After()
{
}
[TBeforeSerialization]
public void Before()
{
}
}
public class TestC33
{
[To2dnd.TDataMember]
public float b2 = 12;
[To2dnd.TDataMember]
public TestC2 tt = new TestC2();
[ To2dnd.TDataMember]
public TestC1[] array1 = new TestC1[] { new TestC1(), new TestC2(), new TestC2() };
[To2dnd.TDataMember]
public object classD = new TestC2();
[To2dnd.TDataMember]
public Type type = typeof(IClass);
}
Видео теста:
Итог
По объему файла, конечно, проигрывает Protobuf и messagepack. Ведь я сохраняю карту типов и не использую хитрые махинации с смещением битов или конвертации строк «byte[] bytes = Encoding.UTF7.GetBytes((string)data.value)». Это дополнительная нагрузка, возможно, потом расширю в виде вариативности. Протестировал обмен данными между фотоном и юнити. Работает как и ожидалось. Ведь я создаю тип относительно сборки, которая является параметром в методе Deserialize.
Готовые решения, которых так много в интернете, не подошли по требованиям. Поэтому пришлось изобрести велосипед. Который оправдал затраченное на него время. Его можно расширять улучшать.
Заключение
Если вы используете примитивные типы, то вам подойдет любой из рассмотренных сериализаторов. Для примитивов я все же предпочел бы Protobuf. Но для сложных типов данных готовые решения подойдут не всегда.
Ссылки
Protobuf
Unity3d Json
MSDN binary formatter
Message pack
Бечмарки
Комментарии (29)
Deranged
09.04.2015 18:40+2Но сложный пример, который будет, далее...
И где? То, что в разделе «Тест»?
… не осилил… binary formatter.
Так указано же в чем ошибка.
SerializationException: Type TestS+TestC1 in assembly Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null is not marked as serializable.derek_streyt Автор
09.04.2015 19:39да в Тесте. А в примере баг для protobuf. реальный пример. Форматтер ложит сборку, а через бинд ее править как то костыльно.
Suvitruf
09.04.2015 18:51+2«Самое логичное решение — это использовать бинарный протокол»
Почему не json? Про размер пока не говорим.Leopotam
10.04.2015 15:25Ну и переименовывать поля при мапинге на типы — размер получается соизмеримым с бинарным, в том числе по скорости работы, поддержке произвольных типов, отсутствию необходимости схемы на обоих концах.
Viacheslav01
10.04.2015 17:17При приличном потоке данных на сериализацию, количество строк подлежащих утилизации зашкаливает.
Leopotam
10.04.2015 19:45Ну работать через StringBuilder и склеивать сообщения, отсылать с фиксированным fps-ом. Да, течь будет, но не сильно страшно. Опять же, зависит от цели. Можно убить всю thread-safe идею и максимально заточить на кеш где это возможно.
yatagarasu
10.04.2015 17:07Не все бинарные данные помещаются в текстовый формат. Про base64 пока не говорим.
derek_streyt Автор
09.04.2015 19:36-2Зачем использовать json при обмене между unity и photon? Можно его без всяких проблем перехватить. Бинарный же нужно еще декодировать. Любой при чем. Потом как решить проблему со сложными типами? Например объекты типа инвентарь?
PsyHaSTe
09.04.2015 23:26+2Перехватить — можно, и что из этого следует? Вы думаете, что кодирование в двоичный вид как-то шифрует данные? Ну и да, если принципиальна двоичность, существует BSON
derek_streyt Автор
09.04.2015 23:45-2Потом как решить проблему со сложными типами?
Leopotam
10.04.2015 15:29В чем проблема типов любой сложности? Это всего лишь KV-пары, корректный мапинг организуется достаточно просто через атрибуты (как, например, было сделано для DataContract / DataMember в SimpleJson).
derek_streyt Автор
10.04.2015 18:02-1Тест для простого примера, для реального примера . Json не сериализует поля типа интерфейс и абстрактный класс. Для этого нужен бубен, пример И наконец, как решать вопрос со сборкой? переименовать? ведь на клиенте и сервере классы хоть и одинаковые, но сборки разные.
Leopotam
10.04.2015 18:12+1Json не сериализует поля типа интерфейс и абстрактный класс. Для этого нужен бубен
Добавить в сериализатор дополнительные условия (пример библиотеки в сорцах я привел выше, один файлик с кучей условных компиляций под разные рантаймы). Это явно быстрее и правильнее чем писать полностью независимый и потенциально нестабильный костыль. Ну и симулировать по сети аналог ремоутинга с проксированием классов — это зло. Данные должны быть максимально простыми. Не должно быть никаких интерфейсов и т.п, с ними есть проблемы с AOT-компиляцией сейчас и будут в будущем при использовании IL2CPP, ибо часть типов выводится в JIT в рантайме. Геймдев — это не enterprise way, тут нужно отказываться от промежуточных абстракций в угоду скорости и переносимости.
как решать вопрос со сборкой? переименовать?
Как связаны сборки на сервере / клиенте? Таки аналог ремоутинга и common-сборки? json хорош тем, что не требует схем. Как дальше будут обслуживаться данные — это проблемы разработчика. Нужно максимально абстрагировать сетевые данные от любых типов в клиенте / сервере — уйдут все проблемы с любыми библиотеками типа протобуфа и т.п.derek_streyt Автор
10.04.2015 18:26Связь через ссылки на классы. По заданным требованиям, сериализовать сложные составные объекты справился только протобуф и стандартный сериализатор. «Данные должны быть максимально простыми», куда проще чем набор байтов.
Leopotam
10.04.2015 18:57+1Типы должны быть простыми — числа, строки, массивы простых типов. Использование кода и на клиенте и на сервере сильно привязывает к конкретному серверу, от этого лучше сразу избавляться, чтобы потом без проблем обеспечивать коннект хоть к photon, хоть к smartfox, хоть к кастомному решению на nodejs. Использование кастомных типов так же допустимо, но следует помнить о том, что в них будут сериализованы тоже только простые типы (ну и вложенные кастомные, рекурсивно). Сервер и клиент должны быть максимально разнесены, сериализация без схемы позволяет это сделать.
derek_streyt Автор
10.04.2015 19:04-1А для чешл тогда были придуманы protobuf message box fb protocol и другие? Очень смутно представляется перескок на другой сервер (например с фотона на sfx еще ладно. они схожи. а вот на другой). В любом случае это не быстро, да и зачем.
Leopotam
10.04.2015 19:32Для того и были придуманы, чтобы абстрагироваться от конкретной реализации и платформы и работать исключительно с переносимыми типами. Нужно стремиться к этому. Как только заработает протобуф — заработает все остальное и это будет означать, что все сделано правильно.
derek_streyt Автор
10.04.2015 19:40-1Изучите внимательней проблему протобуфа. Там баг в подклассе.
Leopotam
10.04.2015 19:41+1В протобуфе нет проблем, проблемы в пользовательском коде :)
derek_streyt Автор
10.04.2015 19:48-1Если бы их не было, я бы не писал свой сериализатор. Пример кода в баг репорте.
derek_streyt Автор
10.04.2015 19:12В этом и идея чтобы минимизировать дублирование кода. Максимум common для ключей.
К серверу привязывает только протокол общения. Но не кто же не мешает использовать массив байтов. Или строку. (Но строки то не дешевые, выбор байтового массива в этом случае более актуален) Если ява, придется продублировать свой сериализатор, тоже и для других языков. Что собвственно тот же message pack и сделал. Сложно представить систему, для msgpak которой не реализован.
slonopotamus
09.04.2015 20:56Самое логичное решение — это использовать бинарный протокол
Самое логичное решение — использовать движок, изначально приспособленный для работы с сетью, не? Примитивную репликацию полей вы допустим на коленке собрали, но вот репликацию движения объектов (плавного, с экстраполяцией, без roundtrip-задержек на инициирующем клиенте, с корректировкой при лагах), например, так задешево сделать уже не получится.
А потом понадобятся фичи типа «а вот эти поля надо реплицировать только игроку, которому принадлежит этот объект, а остальным ни-ни», «а вот по факту обновления этого поля надо на реплике какую-то кастомную логику выполнить» и т.д.
У нас, когда делали прототип игры на Unity, получилось все примерно как у вас — Unity, Photon и горка костылей поверх всего этого. Но, слава ЛММ, это был только прототип, исходники которого после завершения торжественно распечатали и сожгли.derek_streyt Автор
09.04.2015 21:31Задача именно серелизовать объекты. Поля и так поделены на классы. И у каждого игрока своя модель. На сервере модели сереализуются с бд только при включении и выключении сервера. А так они всегда активны, зашел игрок. Его конектит к его модели, та шлет события, мол кто-то куда то пошел или чет построил. Нужно знать о втором игроке, принцип тот же.
yatagarasu
MsgPack вполне должен был справиться, у него единственный ньюанс при работе в Юнити — надо заранее генерить сериализаторы.