diff --git a/story/linux_go_blt_install_quickstart.md b/story/linux_go_blt_install_quickstart.md index 7da307b..398235d 100644 --- a/story/linux_go_blt_install_quickstart.md +++ b/story/linux_go_blt_install_quickstart.md @@ -46,4 +46,96 @@ RPATH ./ Теперь при дистрибуции приложения можно просто положить .so файл библиотеки рядом, и все будет работать! -Вторая кочка, на котрой \ No newline at end of file +Вторая кочка, на которой мне пришлось столкнуться - +то то, что вызов любой своей функции не из 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. Я его использую как +контейнер для важных для игры данных - уровня, состояния рендера, разных тикеров, +каналов для рендера и ввода-вывода итп. Так как он глобальный (или просто передается +по аргументам), то именно в него встроен метод Do. Если нам скажем в пакете где +описывается некий предмет надо нарисовать при его поднятии какой-то супер-эффект +на экране - мы поступаем вот так: + +```go +package item + +import "main" +import blt "some.repo.ru/user/bearlibterminal" + +var State main.GameState + +//функция скажем поднятия описание предмета... +func (item *Item) Pickup() { + + .... + doSuperEffect(State) +} +//and there we go like this: +func doSuperEffect(State main.GameState) { + ... + State.Do(func() { + renderSuperEffect() +// ...do stuff in main thread + }) + ... +} +... +func renderSuperEffect() { + blt.Layer(0) + blt.Print("WAAAGH") + ... +} +``` + +Здесь renderSuperEffect - непосредственная реализация эффекта, doSuperEffect - +запихивает в очередь на выполнения в main thread эту самую реализацию. Которая с успехом +выполняется в main loop. В целом картина именно такая, но больше подробностей можно +найти по ссылкам в комментариях.