Pull to refresh

Comments 16

Спасибо за статьи, было интересно почитать. Есть ли в планах статья про продвижение игры, успехи на GreenLight и т.п.?
Спасибо) Пока такого в планах нету, потому что я не привык рассказывать о том, в чем плохо разбираюсь)
Прогресс на Гринлайт пока неплохой, но каждый голос важен.
В чем удобность команд?
Читал про StrangeIOC, там можно создавать цепочки из сигналов и команд. Но у вас такого не видел.
Еще, возможно, когда в команде много программистов, то удобнее, когда каждая операция в своем файле (проще мерж, лучше видно историю изменений).
Но не нравится мне, что надо создавать класс/файл на каждый чих.
Можете, пожалуйста, поуточнять? Что имеете ввиду под цепочками? У меня команда может вызывать другую команду — чем не цепочка. Для вас система недостаточно сложная? Просто для данной игры более сложная система не нужна. Вот когда делал Генералы — там было сложнее и команды были иерархическими, у команд были дети, сложная система событий, которая позволяла подписаться до команды, в середине команды, после команды, более сложная валидация. Каждому приложению ровно такая сложность, которая необходима, но не больше.

О да, я забыл, ведь создание файлов — это столь сложная операция. Гораздо удобнее каждый раз рыться в файле на тысячи строк. Поиском? Главное преимущество файлов — значительно легче искать то, что необходимо. Когда у тебя весь необходимый код вмещается на одном экране и другой экран просто не нужен) Один файл на 50-100 строк, файлы структурированы. Можно открыть несколько вкладок. Код, который сейчас не нужен — не путается под ногами. Вот мне необходима функциональность А и Г, а между ними — Б и В. Так я просто не открыл лишнее, а только два файла в разный вкладках — а так метаться постоянно видя лишний код.

И какие причины НЕ создавать файлы? Сплошные плюсы ведь)

Ну, в двухста файлах в одной папке тоже ничего хорошего. А группировать их не всегда удобно, иногда важно чтобы классы оставались в одном пространстве имен, а после переноса файлов в другую папку IDE будет активно мешать этому.


Лично я предпочитаю создавать отдельный файл на класс если в этом классе более 7 строк кода.

Что имеете ввиду под цепочками?

commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().To<UpdateScoreCommand>();

commandBinder.Bind(GameEvent.HIT).InSequence()
	.To<CheckLevelClearedCommand>()
	.To<EndLevelCommand>()
	.To<GameOverCommand>();

injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton(); // сигналы - новые, строго типизированные события. 

https://strangeioc.github.io/strangeioc/TheBigStrangeHowTo.html
Это должно проделываться в Composition Root. Вызов одной команды из другой это явно плохая практика.
Кстати, в StrangeIOC команды могут быть асинхронными, в этом случае они конечно упрощают жизнь. Хотя я для этого использовал Promise, которые очень популярные в JS.

файле на тысячи строк

Я не говорил, что надо вообще все в один файл писать. Но создавать файл для каждой функции, еще и в 1-5 строк… это странно для меня. Хотя конечно можно в одном файле написать несколько классов.
Но остается проблема с GC. У вас некоторые команды создавались в каждом update, рано или поздно это может внести свою лепту в понижение fps.
Я не представляю как выглядел мой проект, если бы каждый метод я выносил бы в отдельный класс. Где-то должна быть эта грань.
Не знал, что время редактирования комментария столь ограничено. Немного изменил пример.

commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().To<UpdateScoreCommand>();

commandBinder.Bind(GameEvent.HIT).To<DestroyEnemyCommand>().Once();

commandBinder.Bind(GameEvent.HIT).InSequence()
	.To<CheckLevelClearedCommand>()
	.To<EndLevelCommand>()
	.To<GameOverCommand>();

commandBinder.Bind<ShipDestroyedSignal>().To<ShipDestroyedCommand>(); // сигналы - новые, строго типизированные события.
Вызов одной команды из другой это явно плохая практика

Чем плохая?

Это должно проделываться в Composition Root.

Почему?

Как на меня в данной ситуации это лишнее усложнение, которое не несёт никакого позитива в данном случае.

commandBinder.Bind<ShipDestroyedSignal>().To<ShipDestroyedCommand>(); // сигналы - новые, строго типизированные события.


Я в игре могу подвесится прям на команды как на события. И тоже строготипизированно. Выглядит как-то так:

public void On(ShipDestroyedCommand ev) {
	
}


Но создавать файл для каждой функции, еще и в 1-5 строк

Почему для каждой? Есть много классов, где несколько методов. Да и все классы очень быстро растут. Нету смысла сейчас экономить файлы, чтобы потом их все-равно выносить. И как тогда файлы, которые содержат два-три класса называть? Другой вопрос — нужно ли такое разбиение и может неоторые классы должны нести чуть больше ответственности?

Я не представляю как выглядел мой проект, если бы каждый метод я выносил бы в отдельный класс

Я не выношу каждый метод в отдельный класс, не понимаю о чем вы говорите? Есть и довольно большие классы. Каждый класс у меня отвечает за то что должен и сейчас это довольно мало кода, но дальше он растет и классы растут. Я не могу понять, что вы предлагаете? Ну вот на практике, код, который вы читали выше — какие два класса объеденить в один?

в StrangeIOC команды могут быть асинхронными, в этом случае они конечно упрощают жизнь

Чем упрощают? Что для вас «асинхронные» в данном контексте?)

Почему?

При использование Composition Root, все связи видны как на ладоне. Как минимум это должно упростить жизнь новым программистам на проекте.
Я сейчас читаю книгу «Dependency Injection in .NET». Там об этом написано. Хотя там написано про инициализацию зависимостей, но думаю к сигналам и командам это тоже относится.

Я в игре могу подвесится прям на команды как на события. И тоже строготипизированно. Выглядит как-то так:

В StrangeIoC есть два вида событий: старые событий и новые сигналы. Я это имел ввиду.

Чем упрощают? Что для вас «асинхронные» в данном контексте?)

Команда выполняется в течении нескольких кадров. Проще вызвать другую команду в конце асинхронной команды. Иначе нужен будет или callback или promise.

Чтобы просто не создавать классы размером в 1000 строк, можно использовать шаблон стратегия. Хотя я часто пишу статические классы — утилиты или хелперы.
Суть команд, мне кажется, больше, чем просто декомпозиция большого класса.

Вы упускаете из виду тот факт, что прямые вызовы легко прослеживаются через IDE — а Composition Root в большом проекте иногда нужно еще и найти сначала...

вопрос по классу Command, bool Run и bool IsValid
как обрабатываются различные причины false на клиенте, когда например нужно отобразить разное сообщение пользователю почему та или иная команда не может быть выполнена?
Изначально у меня было что-то вроде такого:

public enum Status {
	Success,
	
	RoomNotEmpty,
	NotEnoughItems,
	CrewMemberIsFreezed
}


public abstract class Command
{
	public Core Core { get; private set; }
	public bool IsValid { get; private set; }
	public Status Status { get; private set; }

	public Command Execute (Core core)
	{
		Core = core;
		Status = Run();
		IsValid = (Status == Status.Success);
		return this;
	}

	protected abstract Status Run ();
}


И тогда каждая команда при ошибке возвращает не false, а код ошибки. Как часть сервера работает прекрасно — можно и узнать все возможные ошибки и сериализуется легко.

protected override Status Run ()
{
	if (Room.Building.Type != BuildingType.Empty) {
		return Status.RoomNotEmpty;
	}
	if (Building.Type == BuildingType.Empty) {
		return Status.CantBuildEmpty;
	}

	if (!new Pay(Building.Config.ConstructionCost).Execute(Core).IsValid) {
		return Status.NotEnoughResources;
	}

	Room.Building = Building;
	return Status.Success;
}


Сейчас же я понял, я никогда не проверяю статус, потому что вьюшка не позволяет запустить команду, которая может вернуть false. Правда, статусы самодокументируемые и отладка с ними легче, так что если бы нас в команде было хотя бы двое — обязательно бы их использовали.
вьюшка не позволяет запустить команду, которая может вернуть false

Это означает, что у вас бизнес-логика дублируется… Нарушение принципа DRY.

Вы правы.

Хотя если строго говорить, то валидация в командах у меня излишняя, а не дублируется (нарушением DRY было бы, если бы убрав эту валидацию, у меня перестало бы работать, а так — функционал игры совершенно не изменится). А сейчас эта валидация просто не нужна для корректной работы игры.

Я пишу эту валидацию скорее для более легкого покрытия тестами, допроверки глупых багов в юай и дополнительной декларативности. А еще потому что это что-то двух разных микросервисов, а там код тоже дублируется.
Вот это я понимаю… гхм, пиар своего проекта. В Гринлайте выглядит интригующе, да и вообще задумка интересная. И когда в Steam появится?
Sign up to leave a comment.

Articles