Установка и работа с BLT на Linux == Про Windows и Mac в контексте связки Go + BLT, я говорить не буду, поскольку не ел устриц. С Linux, которая моя основная рабочая ось - другая история, здесь вносят свой шарм особенности работы линкера. Дело в том, что BLT написана на C. Но! есть готовые биндинги для Go, НО! В Terminal/Include/Go по умолчанию указаны такие флаги линкера CGO (стр 25) ```go // #cgo LDFLAGS: -lBearLibTerminal ``` Что подразумевает глобальную видимость библиотеки. Увы, пока пакета с BLT для распространенных дистрибутивов Linux нет. Поэтому беде нужно помочь руками. #####Первый метод Сначала вручную показать линтеру, что такая библиотека есть, и потом перезагрузить кеш путей к библиотекам (пример для Ubuntu): ```bash $ sudo echo "/path/to/libbearterminal.so" > /etc/ld.so.conf.d/libbearterminal.conf && sudo ldconfig ``` Проблема тут в том, что эту же операцию придется проделать всем, кто захочет запустить ваше приложение с BLT. Вопреки распространенному стереотипу - доля красноглазых пользователей Linux с каждым годом падает, и эта консольная магия для большинства уже некомильфо. #####Второй метод Способ второй, которым воспользовался я, с которым намного проще жить. Редактируем файл с биндингами (BearLibTerminal.go) примерно следующим образом: ```go // #cgo LDFLAGS: -L. -Wl,-rpath -Wl,./ -lBearLibTerminal // #include // #include import "C" ``` (знатоки С, простите если что не так, я этими флагами вообще пользоваться не умею) Далее - собираем минимальное приложение с blt ```go build -o test```. Проверяем, что относительные пути записались в бинарник: ```bash objdump -p test | grep RPATH ``` Результат должен быть таким: ```bash RPATH ./ ``` Ура! Теперь кладем libBearLibTerminal.so прямо в папку с main.go и запускам go run (или скомпилированный бинарник) прям оттуда. Собранные таким образом бинарники будут искать библиотеку в той же папке, где находятся они сами. Теперь при дистрибуции приложения можно просто положить .so файл библиотеки рядом, и все будет работать! Горутины и многопоточность --- Вторая кочка, на которой мне пришлось споткнуться - то то, что вызов любой своей функции не из main thread BLT воспринимает крайне нервно, и сыпет фатальными ошибками. Это неприятно, тк часто Го выбирают именно за многопоточность из коробки. Но справедливости ради точно так же ведет себя и большинство других библиотек связанных с рендером и вводом-выводом, тот же SDL например. Так что воспринимайте это как милую особенность использования CGO. Лекарство тут ровно одно - вызывать сишные foreign functions из main thread. Для реализации этого требования мне показалась полезной следующая конструкция ```go package main import "runtime" ... // Рецепт чтобы убежать от [fatal] 'refresh' was not called from the main thread // https://github.com/golang/go/wiki/LockOSThread func init() { runtime.LockOSThread() } type GameState struct { Mainfunc chan func() } // do запускает функцию f в контексте main thread. func (g *GameState) Do(f func()) { done := make(chan struct{}, 1) g.Mainfunc <- func() { f() f = nil //zero pointer в замыкание done <- struct{}{} } <-done } var State = GameState{ Mainfunc: make(chan func()), //блокирующий канал(!) } // И где-то в Main Loop делаем примерно так: func MainLoop(state GameState) { ... //В этом select обработка ввода, рендер, пеерколючение состояний интерфейса итп for f := range state.Mainfunc { f() }} ... ``` State - это обычный Value Object, экземпляр типа GameState. Я его использую как контейнер для важных для игры данных - географии уровня, состояния объектов и мобов, разных тикеров, каналов для рендера и ввода-вывода итп[1]. Так как он глобальный (или просто передается всюду по аргументам), то именно в него встроен метод Do. Если нам скажем в пакете, где описывается некий предмет, надо нарисовать при его поднятии какой-то супер-эффект на экране - мы поступаем вот так: ```go package item import "main" import blt "some.repo.ru/user/bearlibterminal" var State main.GameState //функция скажем поднятия особенного предмета... func (item *SpecialItem) Pickup() { .... //выполняем строго в main thread State.Do(func() { renderSuperEffect() }) } ... //тут собственно отрисовка эффекта func renderSuperEffect() { ... blt.Layer(0) blt.Print(x,y, "WAAAGH") ... } ``` Здесь renderSuperEffect - непосредственная реализация эффекта, doSuperEffect - запихивает в очередь на выполнения в main thread эту самую реализацию. Которая с успехом выполняется в main loop. В целом картина именно такая, но больше подробностей можно найти по ссылкам в комментариях. [1]: Если такой контейнер аккуратно сериализовать (рекурсивно вместе со всем содержимым) и записать на диск... То потом можно его прочитать и десериализовать. Получив тем самым почти бесплатно Save / Load.