Про Windows и Mac в контексте связки Go + BLT, я говорить не буду, поскольку не ел устриц. С Linux, которая моя основная рабочая ось - другая история, здесь вносят свой шарм особенности работы линкера. Дело в том, что BLT написана на C. Но! есть готовые биндинги для Go,
НО! В Terminal/Include/Go по умолчанию указаны такие флаги линкера CGO (стр 25)
Что подразумевает глобальную видимость библиотеки. Увы, пока пакета с BLT для распространенных дистрибутивов Linux нет. Поэтому беде нужно помочь руками.
Первый метод
Сначала вручную показать линкеру, что такая библиотека есть, и потом перезагрузить кеш путей к библиотекам (пример для Ubuntu):
Код: Выделить всё
$ sudo echo "/path/to/libbearterminal.so" > /etc/ld.so.conf.d/libbearterminal.conf && sudo ldconfig
Проблема тут в том, что
эту же операцию придется проделать всем, кто захочет запустить ваше приложение с BLT. Вопреки распространенному стереотипу - доля красноглазых пользователей Linux с каждым годом падает, и эта консольная магия для большинства уже некомильфо.
Второй метод
Способ второй, более удобный. Редактируем файл с биндингами (BearLibTerminal.go) примерно следующим образом:
Код: Выделить всё
// #cgo LDFLAGS: -L. -Wl,-rpath -Wl,./ -lBearLibTerminal
// #include <stdlib.h>
// #include <BearLibTerminal.h>
import "C"
(знатоки С, простите если что не так, я этими флагами вообще пользоваться не умею)
Далее - собираем минимальное приложение с blt:
Проверяем, что относительные пути записались в бинарник:
Результат должен быть примерно таким:
Ура! Теперь кладем libBearLibTerminal.so прямо в папку с main.go (или рядом с уже скомпилированным бинарником) и запускаем прям оттуда. Теперь бинарники будут искать библиотеку в той же папке, где находятся они сами.
Теперь при дистрибуции приложения можно просто положить .so / .dll / .dylib файл библиотеки рядом, и все будет работать!
Горутины и многопоточность
Вторая кочка, на которой мне пришлось споткнуться - то то, что вызов любой своей функции не из main thread BLT воспринимает крайне нервно, и сыпет фатальными ошибками типа [fatal] 'input_read' was not called from the main thread. Это неприятно, тк часто Го выбирают именно за многопоточность из коробки. Но справедливости ради: точно так же ведет себя и большинство других библиотек связанных с рендером и вводом-выводом, тот же SDL например. Так что воспринимайте это как милую особенность использования CGO.
Лекарство тут ровно одно - вызывать сишные foreign functions из main thread.
Для реализации этого требования мне показалась полезной следующая конструкция:
Код: Выделить всё
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) {
...
for {
//В этом select обработка ввода, рендер, пеерколючение состояний интерфейса итп
select {
...
case f <-State.Mainfunc:
f() //выполняем в main thread вызовы в сишную библиотеку
break
}
...
}
...
State - это обычный Value Object, экземпляр типа GameState. Я его использую как контейнер для важных для игры данных - географии уровня, состояния объектов и мобов, разных тикеров, каналов для рендера и ввода-вывода итп. Так как он глобальный (или просто передается всюду по аргументам), то именно в него встроен метод Do.
Если нам скажем в пакете, где описывается некий предмет, надо нарисовать при его поднятии какой-то супер-эффект на экране - мы поступаем вот так:
Код: Выделить всё
package item
import "main"
import blt "some.repo.ru/user/bearlibterminal"
//в эту переменную при инициализации ставится ссылка на глобальный State
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.
Правда,
я бы рекомендовал пользоваться этим фокусом с осторожностью. Гораздо более надежным представляется просто вызов всех функций использующих C-библиотеку из main thread.
PS Результат использования Go + BearLibTerminal можно посмотреть
здесь