Здравствуйте, vdimas, Вы писали:
V>А почему бы не сделать обновление автоматизированным (типа как делают ORM), где прикладной код писать в колбэках навроде onClanChanged?
V>Тогда новым коллегам вообще не придется возиться с транспортным слоем или возиться по-минимуму.
Да, это возможно. Я упоминал о расширяемом кодогенераторе, и чтобы не быть совсем голословным, приведу пример:
| Скрытый текст |
| public class DeltaGenerator : ICsGenerator
{
string FieldUpdate(RecordForm rec, RecordField field) =>
$@"if (update.{field.csName}.HasValue) this.{field.csName} = update.{field.csName}.Value;";
public void Generate(CsModel model, Module mod)
{
foreach (var rec in mod.Records)
{
var deltaClass = rec.StringAttribute("delta.class", null);
if (deltaClass != null)
{
var deltaFile = rec.StringAttribute("delta.file", mod.csFileName);
var @class = model.File(deltaFile).NamespaceClass(deltaClass);
@class.Partial = true;
@class.Method($@"
public void Update({rec.csTypeName} update)
{{
{rec.Fields.Join(f => FieldUpdate(rec, f))}
}}
");
}
}
}
}
|
| |
Этот скрипт расширения (который я только что набросал за пару минут) генерирует C# код, подобный тому, что я приводил, и который вызвал критику. Объявить здесь же свойства, события, что угодно, или вызвать коллбэки — очевидно, не проблема. Используется следующий образом:
[csharp delta.class="Game.Unit" delta.file="Unit.Generated.cs"]
record UnitUpdate
{
...
}
Partial класс Unit будет сгенерирован в нэймспэйсе Game в файле Unit.Generated.cs.
Однако еще раз скажу, почему этого нет. Чтобы решить задачу в общем случае, нужно в первую очередь стандартизировать способы обновления коллекций. (А их много разных, так как коллекции бывают упорядоченными и неупорядоченными списками, упорядоченными и неупорядоченными словарями, в какие-то элементы только добавляются, из каких-то и удаляются, какие-то всегда обновляются целиком, для каких-то обновление элемента это в свою очередь дельта). Эта работа еще не проделана (а возможно никогда и не будет), и приоритет у нее низкий. Потому что таких функций обновлений в одном проекте всего несколько мест строк на двести-триста в сумме, а писать код реакции (как правило, тривиальный) под условием HasValue, пусть и не так изящно как в коллбэках, но не менее удобно.
V>Еще идея.
V>Если уж у вас собственный DSL и собственный маппинг (кодогенерация) на типы, то можно ввести такую штуку
V>(условный синтаксис):
V>V>type ClanId = Optional<int> where NotSet = 0;
V>type Coord = Optional<uint> where NotSet = ~0; // 0xFFFFFFFF
V>
Сейчас можно так:
define ClanId ?int;
(?int это шорт алиас для Optional<int>; странноватый и не факт что удачный ? перед типом а не после, потому что изначально это был модификатор поля рекорда, а не тип, а потом с развитием системы типов оставили как есть).
Что касается аспектов DSL (про генерируемый C#-код — ниже):
ИМХО, ClanId — должен быть int, а не Optional<int>. Скажем сервисная функция GetClanInfo(ClanId clan_id) не должна принимать NotSet, а GetPlayerInfo может вернуть NotSet в поле ClanId, и я бы предпочел, чтобы в обоих случаях это было отражено в сигнатуре.
Одна из возможных мотиваций для констант после where — экономия трафика — тоже не проходит, так как если не использовать variable encoding, то для NonSet получим 4 байта вместо 1 байта (а часто — бита), что в среднем даст худший результат, а с variable encoding мы стараемся быть осторожнее, так как в случае частого использования и большого числа мелких пакетов нагрузка на cpu может дать больше вреда, чем минимальная экономия трафика — пользы (впрочем, если до этого дойдет, нужно конечно замерять, задумываться об этом имеет смысл лишь для очень частых событий).
V>Для C# генерить примерно такой маппинг:
| Скрытый текст |
| V>V>struct ClanId {
V> public int Value;
V> public bool HasValue { get { return Value != 0; }}
V>}
V>struct Coord {
V> public int Value;
V> public bool HasValue { get { return Value != ~0; }}
V>}
V>void Update(ClanId clanId) {
V> if(clanId.HasValue) ...
V>}
V>
V>Тогда размер структуры в памяти будет минимален и уже имеющийся код можно будет оставить без изменений. |
| |
Здесь мы тоже не можем по типу отличить обязательно наличествующее значение от опционального. Небольшая экономия памяти не кажется достаточной компенсацией.
Ну и не работает обобщенный код для Nullable и ??, что может быть или не быть неудобством в конкретных случаях.
Но честно говоря, я не понимаю, зачем нужно искать замену Optional в случае его использования по самому что ни на есть прямому назначению.
Однако, в любом случае спасибо за предложения. (Без обид, но думать о том, чтобы все бросить и переписать на ASN.1, ANTLR, BLToolkit и т.п. мне давно уже поздно, а о том, как люди воспринимают текущее решение, какие недостатки видят и какие альтернативы предпочитают — это полезно, и хоть я и отстаиваю свою точку зрения, но все идеи и предметную критику записываю. Нужно ли что-то менять в системе апдейт-рекордов — вопрос для нас еще открытый).