From 1ba189697fd031d106648cf31d8e00ef286a7131 Mon Sep 17 00:00:00 2001 From: "anton.gurov" Date: Mon, 11 Nov 2019 20:08:17 +0300 Subject: [PATCH] stiry improvements --- story/go_game_dos_and_donts.md | 170 ++++++++++++++++++++++++++- story/makefile_and_crosscompiling.md | 41 ++++++- 2 files changed, 204 insertions(+), 7 deletions(-) diff --git a/story/go_game_dos_and_donts.md b/story/go_game_dos_and_donts.md index d6455b1..95c6d51 100644 --- a/story/go_game_dos_and_donts.md +++ b/story/go_game_dos_and_donts.md @@ -1,10 +1,172 @@ +Делать +--- +#### декодирование ввода в отдельном потоке + +Этот пункт исключительно только ради удобства. По умолчанию в BLT ввод отдается в виде int-ов, мне гораздо удобнее +получать ввод в виде "<модификатор(ы)+><имя клавиши>", например "Ctrl+Space". + +Для этого пришлось написать map[int]string и пеервести все BLT-токены для клавиатуры в строки, а также написать +простенький обработчик: + +```go +package main + + +func decodeInput(ctx util.ClientCtx) { + var exit = false + + for !exit { + select { + case keycode := <-State.RawInput: + if keycode == blt.TK_NONE { + continue + } + if keycode == blt.TK_CLOSE { + State.Exit <- struct{}{} + return + } + var pressed = "" + var isModifier, _ = util.IntInSlice(keycode, modifiers) + if !isModifier { + + pressed = ui.Scancodemap[keycode] + + if blt.Check(blt.TK_SHIFT) != 0 { + pressed = "Shift+" + pressed + } + if blt.Check(blt.TK_ALT) != 0 { + pressed = "Alt+" + pressed + } + if blt.Check(blt.TK_CONTROL) != 0 { + pressed = "Ctrl+" + pressed + } + + //global hotkeys + switch pressed { + //case "Ctrl+q", "Escape": + case "Ctrl+q": + State.Exit <- struct{}{} + exit = true + return + default: + if pressed != "" { + State.Input <- pressed + } + } + } + } + } +} +``` + +и в main функции обустроить всё для ее работы: +```go +var State = gamestate.GameState{ + Exit: make(chan struct{}, 1), + Input: make(chan string, 1), + RawInput: make(chan int, 1), +} +... + +func main() { + ... + go decodeInput() + ... + var exit = false + for !exit { + + select { + case State.RawInput <- readKeyCode(): + break + case pressed := <-State.Input: + screenMgr.CurrentScreen.HandleInput(pressed) + break + case <-State.Exit: + mainCtx.Logger().Warn().Msg("quitting NOW") + exit = true + break + ... + } + ... +} + +func readKeyCode() int { + if !blt.HasInput() { + return blt.TK_NONE + } + return blt.Read() +} +``` + +Теперь как только что-то приезжает к канал State.Input - просто передаем это в обработчик ввода текущего экрана. +И пользуемся удобными обозначениями клавиш. + +#### Реализация FPS и троттлинг + +FPS в случае использования скажем SDL напрямую может уехать за несколько сотен. Это не лучшим способом скажется +на утилизации CPU. + +Иногда нужен отдельный FPS для некоторых видов анимации (в моем случае есть слегка анимиррованные тайлы). + +реализуется это достаточно просто: + +```go +type TerrainRenderer { + ... + animateTiles *time.Ticker //поле для тикера + ... +} + -декодирование ввода в отдельном потоке +func NewTerrainRenderer() *TerrainRenderer { + t := &TerrainRenderer{} + ... + t.animateTiles = time.NewTicker(time.Second / 12) + return t +} +... +var redraw = true +... -Реализация FPS и троттлинг +//Это запускается в main thread ДО main loop в отдельном потоке - go terrainRenderer.Listen() +func (tr *TerrainRenderer) Listen(state gamestate.GameState) { + for { + select { + case <-state.FovRecompute: //глобальное состояние говорит что видимость изменилась + fovRecompute = true + case <-state.Redraw: // или что просто надо совежить картинку + redraw = true + case <-tr.animateTiles.C: // а вот внутренний тикер говорит нам что пора играть следующий кадр анимации + redraw = true + } + } +} +... +//А это собственно отрисовка ландшафта, вызывается в main thread +func (tr *TerrainRender) Render() { + + if redraw { + redraw = false + //рисуем ландшафт на карте + ... + } + ... +} +``` -Отдельный FPS для некоторых видов анимации (в моем случае анимиррованные тайлы) +#### Каналы состояний и их Listеner-ы -Каналы состояний и их Listеner-ы \ No newline at end of file +Ну вобщем-то из кода выше видно, как это работает. Для состояния движка ок, для состояния игры - кмк больше подойдет обычная или timed queue. + +Не делать +== + +- Замыкания (не зная точно, где и как вы их уничтожите). Просто наберите в гугле golang closure memleak + +- reflect в main loop. Лишь **только** выкинув рефлкесию и больше ничего не делая - я снизил потребление CPU приложением +**вдвое**. Это удобная штука, не спорю, но пользоваться ей надо при загрузке ресурсов, при сохранении/загрузке состояния +приложения - т.е. при разовых операциях. Как оказалось, она _очень_ дорогая по CPU. Кто пользоуется ей в main loop, ORM +и прочих нагруженных местах - да будет предан анафеме. + \ No newline at end of file diff --git a/story/makefile_and_crosscompiling.md b/story/makefile_and_crosscompiling.md index e3cfa87..72cd100 100644 --- a/story/makefile_and_crosscompiling.md +++ b/story/makefile_and_crosscompiling.md @@ -1,10 +1,45 @@ Автоматизация сборки и тестирование === -- Почему это важно: мелочи сжирают кучу времени. Не позволяйте им это делать! -- настройка под Linux: все внешние либы собраны и включены в монорепо (дело вкуса) -- Go-специфичные вещи: glide, go mod +Мелочи сжирают кучу времени. Не позволяйте им это делать! + +Почему это важно +--- +Про сборку под разные ОС я даже уюеждать не буду - аудитория рогаликов мало того что крохотная, так еще и сильно +сегментирована по осям. Go почти бесплатно дает вам возможность сборки под все мажорные оси, пользуйтесь этим - и +потенциально в разы больше народа ознакомится с вашим творением. + +Почему это очень важно +--- +Запуск тестов и сборки руками отнимает вроде бы немного времени. Но это настолько частая операция (вы же собираете новые +версии после багфиксов, да?!), что даже жалкие 5 минут после 12 сборок в день превращаются в ЧАС потерянного времени. А +время вам никто, никогда, ни при каких обстоятельствах не вернет. + +Подготовка +--- +Дальше я буду рассказывать про настройку под Linux, поскольку это моя основаня рабочая ось. Но на самом деле настройка +под Windows/MinGW и Мак не так уж и сильно отличаются. + +###### соберите внешние библиотеки под каждую целевую для сборки ось, одной версии, и положите их в репозиторий. + +Каждый раз выкачивать скажем BLT от разработчика - ненадежно, менять версию библиотеки на новую и несовместимую вы точно не +будете, а готовая версия, уже собранная вас наверняка устраивает по фичам, правда ведь? + +Вот и заморозьте версию и оставьте эталон там, где он не потеряется. Можно прям в системе контроля версий (Git). + +###### Потратьте день на изучение вендоринга в Go + +Прежде всего речь про go mod. Glide, godep и прочие - официально уже более не поддерживаются. +Только не просто пролистайте мануал, а прям создайте пакет с hello world, опубликуйте на гитхабе, попробуйте его в +другой проект импортировать, поймите зачем нужны теги и что такое семантическое версионирование. + +Если вы профессионально программируете (или планируете это делать) на Go - этот потраченный день вернется вам отсуствием +в вашей жизни недель головной боли из-за того, что "сборка внезапно сломалась и не работает". + +Как выжить при кросс-компиляции, краткий курс +--- - Кросскомпиляция, CGO для Mac и Linux. CGO_ENABLED=1, mingw, локальная видимость библиотек + - Makefile и нафига он нужен - Таргеты: Убираем бардак за собой - distclean, build - Автоматическое тестирование, testify. Не ленитесь писать тесты!