Комментарии 27
НЛО прилетело и опубликовало эту надпись здесь
Binding WPF такой же:)
0
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Сам пишу приблизительно таким подходом. Меня смущает куча public полей в GameState, которые просто меняются командами, но, теоретически, их могут менять другие программисты прямо из вьюшек.
[System.Serializable]
public class GameState
{
public int coins;
}
0
Я пришел в итоге к выводу, что если такие люди есть, и они не понимают объяснений, что так делать не надо, то с ними лучше не работать. По моему опыту большинство людей достаточно умны, чтобы так не делать. Ну а новичкам всегда код-ревью делать.
Так же чудаки всегда умудрятся любую защиту сломать =)
Если же это действительно критично, и если уж совсем по кананам, то это фиксится просто:
1. Делаем IReadonlyGameState
2. В stateUpdated кидается IReadonlyGameState.
У этого подхода есть большой минус.
Если GameState содержит вложенные объекты, то их тоже нужно делать с двумя интерфейсами мутабельным и иммутабельным. Это выливается в кучу проблем.
Если бы C# поддерживал что-нибудь типа const в С++, то ситуация была бы намного проще =)
Так же чудаки всегда умудрятся любую защиту сломать =)
Если же это действительно критично, и если уж совсем по кананам, то это фиксится просто:
1. Делаем IReadonlyGameState
public interface IReadonlyGameState
{
int coins { get; }
}
[System.Serializable]
public class GameState : IReadonlyGameState
{
public int coins { get; set; }
}
2. В stateUpdated кидается IReadonlyGameState.
У этого подхода есть большой минус.
Если GameState содержит вложенные объекты, то их тоже нужно делать с двумя интерфейсами мутабельным и иммутабельным. Это выливается в кучу проблем.
Если бы C# поддерживал что-нибудь типа const в С++, то ситуация была бы намного проще =)
+1
Полагаю, если вы говорите о реактивном программировании, то стоит делать стейт иммутабельным, а метод Execute команды должен возвращать новый стейт с измененными параметрами. Но данный подход создает определенные проблемы с затратами ресурсов системы.
0
Реактивное программирование не обязательно должно быть полностью по ФП.
Иммутабельность стейта в принципе важная и удобная штука. Но на моей практике — больше геморроя, чем пользы.
Так то в ивенты можно отдавать отдельные иммутабельные подмножества стейта.
В любом случае — это довольно субъективный взгляд и каждый решает как ему удобнее.
Что касается того, что каждый раз нужно создавать новый стейт — лишние аллокации в играх ни к чему хорошему обычно не приводят :), а если стейт жирный, то можно на хорошие такие грабли наступить.
Иммутабельность стейта в принципе важная и удобная штука. Но на моей практике — больше геморроя, чем пользы.
Так то в ивенты можно отдавать отдельные иммутабельные подмножества стейта.
В любом случае — это довольно субъективный взгляд и каждый решает как ему удобнее.
Что касается того, что каждый раз нужно создавать новый стейт — лишние аллокации в играх ни к чему хорошему обычно не приводят :), а если стейт жирный, то можно на хорошие такие грабли наступить.
+1
НЛО прилетело и опубликовало эту надпись здесь
Ну я за эти годы повидал несколько крупных проектов. И там стейт был далеко не маленьким. Например, были прецеденты, когда все получаемые в игре вещи/апгрейды складывались в стейт. Таким образом, если юзер хайлевел, и ему некуда их утилизировать, то они накапливались.
Хоть они и стакались, количество уникальных вещей со временем все равно росло сильно. В итоге стейт очень разрастался.
Это проблема и архитектуры и дизайна, но от этого никто не застрахован.
Хоть они и стакались, количество уникальных вещей со временем все равно росло сильно. В итоге стейт очень разрастался.
Это проблема и архитектуры и дизайна, но от этого никто не застрахован.
0
то стоит делать стейт иммутабельным
А как на Шарпах удобно сделать стейт иммутабельным? Ну вот мутабельно я меняю его, допустим, так:
state.ships[1].room[5].level++;
Как сделать то же самое иммутабельно? Чтобы вернулся новый стейт с измененным только кораблем 1 и комнатой 5?
0
Адекватным подходом — никак. Либо искать реализации каких-либо древовидных структур, которые скрывают возможность изменения стейта за методами, возвращающими новую ссылку на стейт, а под капотом по-умному генерируют дифф над предыдущим состоянием.
0
Эх. У меня просто как раз задача, которая идеально ложится на иммутабельный стейт. Но пока приходится просто весь копировать и менять кусочки. Благо, стейт маленький.
0
Не уверен в адекватности подхода, но по идее выглядеть это будет так:
class State {
public ImmutableArray<Ship> Ships { get; }
public State (ImmutableArray<Ship> ships) {
this.Ships = ships;
}
}
class Ship {
public ImmutableArray<Room> Rooms { get; }
public Ship (ImmutableArray<Room> rooms) {
this.Rooms = rooms;
}
}
class Room {
public int Level { get; }
public Room(int level) {
this.Level = level;
}
}
// ...
Ship[] ships = state.Ships.ToArray();
Room[] rooms = ships[shipIndex].Rooms.ToArray();
rooms[roomIndex] = new Room(rooms[roomIndex].Level + 1);
ships[shipIndex] = new Ship(ImmutableArray.ToImmutableArray(rooms));
return new State(ImmutableArray.ToImmutableArray(ships));
Но как было сказано выше, тут многовато аллокаций.
0
Ship[] ships = state.Ships.ToArray(); Room[] rooms = ships[shipIndex].Rooms.ToArray(); rooms[roomIndex] = new Room(rooms[roomIndex].Level + 1); ships[shipIndex] = new Ship(ImmutableArray.ToImmutableArray(rooms)); return new State(ImmutableArray.ToImmutableArray(ships));
Ну вы же понимаете, что это просто кошмар? Мне надо было 2 минуты чтобы понять, не ошиблись вы где-то. Как это поддерживать вообще? А если в комнатах появяется еще три поля? А если массив комнат перенесется в филд `interior` внутри корабля? Ваш код ни отрефакторить, ни новую фичу добавить. И он требует писать в
state.ships[1].interior.room[5].level++;
И да, вы написали в 5 раз больше строчек и в 8,5 раз больше символов, чем я. Игры и так крайне сложные механизмы, а если их еще сделать в 5 раз сложнее — как их разрабатывать вообще?
0
НЛО прилетело и опубликовало эту надпись здесь
Я не говорю, что так делать нужно. Скорее это была просто деманстрация. И я согласен с вами, что это выглядит уж очень громоздко.
Как альтернативное решение могу предложить, с помощью пространств имён сделать состояние иммутабельным снаружи, а внутри неймспейса состояния и команд оставить возможность изменения объектов.
0
Как же аллокации при создании команд (команда — класс, а не структура)? Ведь в реальном проекте может создаваться несколько команд за кадр.
Можно ли команды реализовать в виде struct, избегая boxing(при приведении к интерфейсу) и не теряя полиморфизма при исполнении?
Можно ли команды реализовать в виде struct, избегая boxing(при приведении к интерфейсу) и не теряя полиморфизма при исполнении?
0
Когда я проводил исследования, аллокации от команд были незначительными, по сравнению с аллокациями при поддержке иммутабельности.
Команды можно сделать struct, но тогда придется пожертвовать, например, строгой типизацией. Ведь чтобы хранить контейнер полиморфных команд, их все равно придется боксить. Поэтому команду придется привести к одному классу, а тип внутри команды обозначить enum. Параметры же придется организовать в виде какого-нибудь Dictionary<string, object>.
В таком случае команда будет выглядеть примерно так:
Если же хранить команды в контейнере не надо, то можно писать отдельные типы команды, а сам тип определять по cmd.GetTypeCode() (обычный GetType боксит), по нему же выбирать какой хэндлер вызвать.
Команды можно сделать struct, но тогда придется пожертвовать, например, строгой типизацией. Ведь чтобы хранить контейнер полиморфных команд, их все равно придется боксить. Поэтому команду придется привести к одному классу, а тип внутри команды обозначить enum. Параметры же придется организовать в виде какого-нибудь Dictionary<string, object>.
В таком случае команда будет выглядеть примерно так:
public struct Command
{
public enum Type {
AddCoins
}
public Type type;
public Dictionary<string, object> parameters;
}
...
void HandleCommand(Command cmd)
{
switch (cmd.type)
{
case AddCoins:
...
break;
}
}
Если же хранить команды в контейнере не надо, то можно писать отдельные типы команды, а сам тип определять по cmd.GetTypeCode() (обычный GetType боксит), по нему же выбирать какой хэндлер вызвать.
0
Да, этот вариант скорее всего не даст особого выигрыша, из-за аллокаций на Dictionary и параметры команды все равно придется боксить к object. И использовать сложнее.
Но вы натолкнули на идею проверить, во сколько обойдется замена struct на class в одном из проектов с достаточным количеством сообщений, за что отдельное спасибо!
Статистика показала, что в следующем проекте я спокойно могу использовать команды-классы: за 4 мин передано 13103 сообщений, на которые всего аллоцировано 221 Кб.
Но вы натолкнули на идею проверить, во сколько обойдется замена struct на class в одном из проектов с достаточным количеством сообщений, за что отдельное спасибо!
Статистика показала, что в следующем проекте я спокойно могу использовать команды-классы: за 4 мин передано 13103 сообщений, на которые всего аллоцировано 221 Кб.
+2
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Развязываем игровой код с помощью паттерна Command, и дебажим, летая на машине времени