Пишу на Java под Android. Имеется некий класс GameThread, который наследуется от Thread. Он содержит приватную переменную isExit:
public final class GameThread extends Thread
{
private boolean isExit;
Так же у него есть метод, который позволяет устанавливать состояние работы потока (нужно ли ему продолжать работать или завершаться):
public void setExit(boolean _isExit)
{
synchronized(this)
{
isExit=_isExit;
}
}
Сама функция потока выглядит следующим образом:
@Override public void run()
{
boolean isLocalExit;
synchronized(this)
{
isLocalExit=isExit;
}
while(!isLocalExit)
{
// Начало секретного кода
// ...
// Конец секретного кодаsynchronized(this)
{
isLocalExit=isExit;
}
}
}
Далее есть другой класс GameView, который унаследован от SurfaceView (это уже Android API). В нём я реализовал функцию, которая запускается в момент получения от Activity события onStart():
Когда Activity стартует, я говорю потоку что он может работать gameThread.setExit(false) и запускаю его gameThread.start(). Когда же поверхность уничтожается (а она уничтожается когда Activity останавливается), то я говорю потоку что ему пора завязывать gameThread.setExit(true) и ожидаю этого завершения gameThread.join(). С этим никаких нареканий нет. Первый запуск и остановка Activity происходит без проблем. Ошибка появляется когда я не по новой запускаю программу, а когда она просто восстанавливается (Activity же запускаются, останавливаются — обычное дело), так то она всё время в памяти висит. И тогда у меня происходит ошибка на строчке:
gameThread.start();
Вот что она гласит:
10-08 12:34:27.629: ERROR/AndroidRuntime(6341): FATAL EXCEPTION: main
java.lang.IllegalThreadStateException: Thread already started.
at java.lang.Thread.start(Thread.java:1045)
То есть поток уже запущен. Это как так, я ведь его завершал и дожидался этого?! Что тут можно придумать?
Спасибо за внимание!
P.S.: выслушаю любые комментарии и замечания по самому ведению кода (архитектуре).
Re: Проблема с завершением и запуском потока по новой
Здравствуйте, s3dworld, Вы писали:
S>Пишу на Java под Android. Имеется некий класс GameThread, который наследуется от Thread. Он содержит приватную переменную isExit:
А что готовых движков под Android нет, где это уже нормально сделано?
Re[2]: Проблема с завершением и запуском потока по новой
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, s3dworld, Вы писали:
S>>Пишу на Java под Android. Имеется некий класс GameThread, который наследуется от Thread. Он содержит приватную переменную isExit: B>А что готовых движков под Android нет, где это уже нормально сделано?
Думаю есть. Но у меня нет времени в них разбираться. Мне нужно то сделать всего минимум. Есть что по проблеме?
Re: Проблема с завершением и запуском потока по новой
Здравствуйте, s3dworld, Вы писали:
S>Думаю есть. Но у меня нет времени в них разбираться. Мне нужно то сделать всего минимум. Есть что по проблеме?
Переделайте на wait() при выходе и notify() вместо start();
А зачем такая заморочка с synchronized, только чтобы передать флаг? volatile не лечит?
Re: Проблема с завершением и запуском потока по новой
Здравствуйте, s3dworld, Вы писали:
S>То есть поток уже запущен. Это как так, я ведь его завершал и дожидался этого?! Что тут можно придумать?
А нельзя просто новый экземпляр создать?
Re: Проблема с завершением и запуском потока по новой
RomikT > Один и тот же объект Thread нельзя запустить два раза. Надо создавать новый.
Спасибо, важное замечание!
Blazkowicz > А нельзя просто новый экземпляр создать?
Будет проблемно. У меня GameThread помимо этого ещё создаёт множество других классов. Либо мне разъединить это и состряпать GameThread и Game (сейчас это всё в одном).
> Переделайте на wait() при выходе и notify() вместо start();
Сейчас почитаю про это.
> А зачем такая заморочка с synchronized, только чтобы передать флаг? volatile не лечит?
Спасибо, это для меня новое! Сейчас переделаю. Это действительно важное сведение.
Alekseymir > Создавай новый поток, используй какой нить wait/notify механизм, пулы потоков если они есть в Андроиде
Сейчас про это почитаю. Ну пулы потоков мне не нужны, по сути поток я делаю для обработки игрового цикла, так как на Android обязательно графика должна рисоваться в отдельном от Activity потоке.
Re[2]: Проблема с завершением и запуском потока по новой
А вот на счёт volatile. Сама по себе то она просто указывает не лесть в кэш, а значение читать и записывать на прямую в память. Возможно для boolean типа это будет и в самый раз (зависит от того, может ли поток остановиться на половине считывания этого значения, но видимо один байт (если в ней один байт) он будет читать за раз). А как лучше поступить, если хочу чтобы у меня не было путаницы в доступе с другими объектами. Сейчас опишу что я хочу...
У меня есть два потока:
1. Поток Activity
2. Поток логики
(1) вызывается сам по себе при наступлении каких-то событий:
— Activity перекрыто, возрождено;
— на экране пальцем что-то сделали;
— нажали кнопку
(2) работает пока Activity активно. И его задача — это обновление состояния игры и вывод его на экран. А состояние может обновляться несколькими способами:
— с течением времени;
— в результате произошедших событий от Activity
С течением времени — это очень просто. А что касается другого способа, то тут давайте рассмотрим. Пока Activity активно, у меня вечно крутится цикл из обновления состояния и вывод его на экран. По сути это работает параллельно (особенно если в устройстве несколько ядер). Как только я тыкнул пальцем на экран, у меня код начал выполняться не потоком обновления состояния (тот что у меня был GameThread), а потоком Activity. И в результате координаты куда тыкнули мне должны попасть как-то и в поток логики. В общем тут получается что такое нажатие может чем-то навредить другому потоку, так как он постоянно читает/записывает какие-то данные, а тут ещё эти же данные другой поток захочет изменить. Ну и вполне понятно что тут нужна синхронизация.
Как я решил сделать, я последовал примеру из Win32 API — как там это работается с окнами (как окна получают сообщения и обрабатывают их). У меня есть класс InputManager, который выглядит следующим образом:
public final class InputManager
{
private LinkedBlockingQueue<TouchEvent> qTouch;
private LinkedBlockingQueue<KeyboardEvent> qKeyboards;
public InputManager()
{
qTouch=new LinkedBlockingQueue<TouchEvent>();
qKeyboards=new LinkedBlockingQueue<KeyboardEvent>();
}
public void pushTouch(MotionEvent _event)
{
try
{
qTouch.put(new TouchEvent(_event));
}
catch(InterruptedException _exception)
{
}
}
public void pushKeyboard(KeyEvent _event)
{
try
{
qKeyboards.put(new KeyboardEvent(_event));
}
catch(InterruptedException _exception)
{
}
}
public TouchEvent popTouch()
{
return qTouch.poll();
}
public KeyboardEvent popKeyboard()
{
return qKeyboards.poll();
}
}
Поток Activity вносит события, а поток обновления игры забирает их. Вообще так то всё работает. И проблему в первом посте я решил за счёт разъединения GameThread на GameThread и Game (первый каждый раз пересоздаётся). Но что касается этого сообщения, то я беспокоюсь что такой способ может быть достаточно медленным.
Как бы Вы мне посоветовали оптимизировать программу для максимального быстродействия?
Re[2]: Проблема с завершением и запуском потока по новой
Здравствуйте, Blazkowicz, Вы писали:
S>>Пишу на Java под Android. Имеется некий класс GameThread, который наследуется от Thread. Он содержит приватную переменную isExit: B>А что готовых движков под Android нет, где это уже нормально сделано?
Поддерживаю. Со времен J2ME что-то должно меняться в лучшую сторону. Сейчас этот код — какой-то жесткий флэшбэк.
Re: Проблема с завершением и запуском потока по новой
Здравствуйте, s3dworld, Вы писали:
S>Пишу на Java под Android. Имеется некий класс GameThread, который наследуется от Thread. Он содержит приватную переменную isExit:
Как уже сказали, не стоит каждый раз убивать и заново стартовать нить. Вместо этого надо ее усыплять и пробуждать. Если на Андроиде действительно приходится вручную управлять нитями для анимации, то можно подсмотреть тут: http://www.rsdn.ru/article/java/J2MEFirstSteps.xml#EIHAC
Здравствуйте, Donz, Вы писали:
D>Поддерживаю. Со времен J2ME что-то должно меняться в лучшую сторону. Сейчас этот код — какой-то жесткий флэшбэк.
Вот тут хороший разбор по теме http://www.koonsolo.com/news/dewitters-gameloop/
С учетом такого количества нюансов глупо изобретать gameloop заново. Часто пишут, что гораздо лучше не делать game loop вообще, а реализовать полностью событийную модель.
Re[4]: Проблема с завершением и запуском потока по новой
Donz > Как уже сказали, не стоит каждый раз убивать и заново стартовать нить. Вместо этого надо ее усыплять и пробуждать.
А почему не убивать и создавать, если этот процесс происходит очень редко (сворачивание игры)?
> Если на Андроиде действительно приходится вручную управлять нитями для анимации, то...
Ну там нужен отдельный поток для рисования. И для того, чтобы потом логику игры не синхронизировать с рисованием, я всё это рассчитываю в этом же потоке. А так как поток встал на цикл, то я могу замерять текущее время System.currentMillics() (или как-то так, пишу по памяти) и находить прошедшее время. С учётом разницы времени я могу изменять рисунки и будет анимация.
Blazkowicz > Часто пишут, что гораздо лучше не делать game loop вообще, а реализовать полностью событийную модель.
Ну как же не делать, ведь игровой мир обновляется не только с возникновением событий от Activity, но и с течением времени. Например какой-то персонаж двигается. Разумеется можно для его перемещения подписаться на какой-то таймер, но я не понимаю что мы на этом сэкономим.
Правда сейчас меня больше вопрос синхронизации волнует. Не будет ли мой метод менее эффективный в быстродействии? Может стоит как-то по другому выкрутиться? Или же вместо LinkedBlockingQueue использовать что-то другое?
Re[5]: Проблема с завершением и запуском потока по новой
Здравствуйте, s3dworld, Вы писали:
S>Ну как же не делать, ведь игровой мир обновляется не только с возникновением событий от Activity, но и с течением времени.
Таймеры.
S>Например какой-то персонаж двигается.
"Интерполяция и "предсказание"
S>Разумеется можно для его перемещения подписаться на какой-то таймер, но я не понимаю что мы на этом сэкономим.
Например, скорость движения не будет зависеть от производительности железа. Для андроида особенно актуально.
Почитайте внимательнее: http://www.koonsolo.com/news/dewitters-gameloop/
S>Правда сейчас меня больше вопрос синхронизации волнует.
Как вы умудряетесь в двух потоках волноваться из-за синхронизации?
S>Не будет ли мой метод менее эффективный в быстродействии?
Чем что?
Re[6]: Проблема с завершением и запуском потока по новой
Blazkowicz > Например, скорость движения не будет зависеть от производительности железа.
При вызове обновления состояния я передаю компоненту два значения во времени:
1. Сколько компонент уже работает (workTime)
2. Сколько прошло времени с его последнего обновления (elapsedTime)
Оперируя этими двумя значениями я могу изменять анимацию так, чтобы она изменялась во времени одинаково на всех устройствах. Следовательно тогда какое преимущество мне дадут таймеры по сравнению с моим способом?
> "Интерполяция и "предсказание"
Зачем? У меня не сетевая игра.
> Как вы умудряетесь в двух потоках волноваться из-за синхронизации?
Тут дело не в количестве потоков. Сколько потоков не было бы, они могут повредить данные, если остановить поток (Android ведь многозадачная ОС) во врем записи данных куда-то (то есть он полностью не запишет, а только часть) и в это время другой поток будет это считывать и считает не полностью изменённые данные, а какую-то мутацию. Понимаете к чему я? Не кидать же всё это на удачу.
> Чем что?
Ну это пока единственный мой способ. Может кто-то делает по другому и его вариант быстрее.
Re[7]: Проблема с завершением и запуском потока по новой
Здравствуйте, s3dworld, Вы писали:
S>При вызове обновления состояния я передаю компоненту два значения во времени: S>1. Сколько компонент уже работает (workTime) S>2. Сколько прошло времени с его последнего обновления (elapsedTime) S>Оперируя этими двумя значениями я могу изменять анимацию так, чтобы она изменялась во времени одинаково на всех устройствах. Следовательно тогда какое преимущество мне дадут таймеры по сравнению с моим способом?
>> "Интерполяция и "предсказание" S>Зачем? У меня не сетевая игра.
Ну, это и есть, то что вы описали выше.
>> Как вы умудряетесь в двух потоках волноваться из-за синхронизации? S>Тут дело не в количестве потоков. Сколько потоков не было бы, они могут повредить данные, если остановить поток (Android ведь многозадачная ОС) во врем записи данных куда-то (то есть он полностью не запишет, а только часть) и в это время другой поток будет это считывать и считает не полностью изменённые данные, а какую-то мутацию. Понимаете к чему я? Не кидать же всё это на удачу.
Нет, не очень понимаю к чему это. Есть два потока. Один во время отрисовки читает модель и отображает её текущее состояние. Второй изменяет это состояние.
Зачем здесь нужна точная целостность, мне не понятно. Ну, допустим, что-то не успело отрисоваться в текущем фрейме, отрисуется в следующем. На что это вообще влияет? Можете пример привести?
Второе что мне не понятно, это почему у вас поток отрисовки тоже свой собственный. Разве нет у Андроида своего UI потока, в котором всё и отображается?
Re[8]: Проблема с завершением и запуском потока по новой
Blazkowicz > Ну, это и есть, то что вы описали выше.
Ну как бы интерполяция предсказание используются при нехватке данных (например в сетевой игре при задержках да и без них). А у меня данные есть всегда, следовательно этот способ отпадает.
На самом деле может быть использование таймеров и было бы быстрее в производительности (тут я не эксперт), но у меня уже вся игровая система завязана на времени от System.currentMillics(). При этом есть сцены, есть объекты сцены. Иерархия наследования. У каждой сцены и у каждого объекта сцены есть куча абстрактных методов, которые вызываются регулярно (тот же метод update()). Так что просто напросто под таймеры мне бы пришлось долго переделывать (да и нужно ли?).
> Нет, не очень понимаю к чему это. Есть два потока. Один во время отрисовки читает модель и отображает её текущее состояние. Второй изменяет это состояние. > Зачем здесь нужна точная целостность, мне не понятно. Ну, допустим, что-то не успело отрисоваться в текущем фрейме, отрисуется в следующем. На что это вообще влияет? Можете пример привести?
У меня дополнительный поток не только рисует, он ещё и обрабатывает логику игры с течением времени. Следовательно и он сам изменяет те значения, которые может поменять поток, вызванный от Activity. Когда я тыкаю пальцем в экран, Activity генерирует событие и за время своего потока вызовет у сцены метод onTouch(). И я уже буду менять, например направление движение игрока в потоке Activity. В этот момент мой вторичный поток с течением времени тоже будет что-то менять сам. При этом ему надо тоже изменить направление игрока (подкорректировать и прочее). И в результате эта взаимная запись/чтение с неизвестными остановками может привести к тому, что вектор направления изменит своё значение на такое, что глаза ослепнут. Это я на примере вектора направления, а так то что угодно может быть. Ведь два потока используют общий код. Ну а сейчас я от потока Activity складываю все события LinkedBlockingQueue и завершаю своё путешествие. А вторичный поток каждый цикл извлекает из LinkedBlockingQueue события и выполняет их всё в тех же функциях, но за своё время — то есть никаких мутирующих данных.
> Второе что мне не понятно, это почему у вас поток отрисовки тоже свой собственный. Разве нет у Андроида своего UI потока, в котором всё и отображается?
Вот цитата из Android Develop:
The SurfaceView is a special subclass of View that offers a dedicated drawing surface within the View hierarchy. The aim is to offer this drawing surface to an application's secondary thread, so that the application isn't required to wait until the system's View hierarchy is ready to draw. Instead, a secondary thread that has reference to a SurfaceView can draw to its own Canvas at its own pace.
То есть нужен вторичный поток.
Я не говорю что у меня идеальная архитектура. И рад если Вы её попинаете. Тем самым я улучшу код. Так что пишите любые замечания и комментарии.
Re: Проблема с завершением и запуском потока по новой
Описание архитектуры прочитал, возник вопрос — почему бы не разделить задачи между потоками? То есть, один поток просто отрисовывает то, что лежит в каком-то объекте, например, 30 (60?) раз в секунду.
Второй поток каким-то образом меняет значение того объекта — не важно, по времени или по событиям из клавиатуры. Грубо говоря, нет разницы, меняется ли состояние игрового мира по времени или в результате действий игрока.
Тогда у вас получится строгое разделение и не будет путаницы с записью/чтением. (Что-то типа MVC)