Я делаю игру в android, используя opengl-es, используя несколько потоков:
class World{
protected static final AtomicInteger entityLock = new AtomicInteger();
private GameEntity entities[];
public World(){
// populate game world with entities
// executed on main thread
addEntity(new GameEntity("tank"));
addEntity(new GameEntity("rifleman"));
addEntity(new GameEntity("rifleman"));
}
void update(){
synchronized(entityLock){
for(int i = 0;i<entities.length;i++){
// move entity to new position
// executed on PhysThread
entities[i].updatePosition();
}
}
if(entity.isDead(){
// remove entity. Enter sync block inside removeEntity() method
removeEntity(entity);
}
}
void draw(GL10 gl){
synchronized(entityLock){
for(int i = 0;i<entites.length;i++){
// draw models
// executed on GLThread
Vector3 entityPosition = entities[i].getPosition();
gl.glTranslatef(entityPosition.x, entityPosition.y, entityPosition.z);
entities[i].draw();
}
}
}
public void addEntity(GameEntity entity){
synchronized(entityLock){
// arrays stuff
}
}
public void removeEntity(GameEntity entity){
synchronized(entityLock){
// arrays stuff
}
}
}
class MyRenderer implements GLSurfaceView.Renderer{
World world;
public MyRenderer(World world){
this.world = world;
}
public void onDrawFrame(GL10 gl) {
// executed on GLThread
world.draw(gl);
}
}
class PhysThreadRunnable implements Runnable{
private long tickRate = 30;
private World world;
private PhysThreadRunnable(World world){
this.world = world;
}
protected void setTickRate(long tickRate){
this.tickRate = tickRate;
}
public void run() {
while(true){
try {
// executed on PhysThread
world.update();
Thread.sleep(1000/tickRate);
} catch (InterruptedException e) {
return;
}
}
}
}
MyActivity extends Activity{
@Override
public void onCreate(Bundle savedInstanceState) {
World world = new World();
// sets up the game world, populates it with entities
// set up GLSurfaceView (simplified)
setContentView(R.layout.main);
GLSurfaceView mGLView = findViewById(R.id.myGLSurfaceView);
mGLView.setRenderer(new MyRenderer(world));
// start phys thread
PhysThreadRunnable physThreadRunnable = new PhysThreadRunnable(world);
Thread physThread = new Thread(physThreadRunnable);
physThread.start();
}
}
У меня есть проблема, когда иногда (но не каждый раз), когда я запускаю игру, PhysThread застревает в ожидании блокировки, которая будет выпущена (т.е. когда я иду отлаживать и приостанавливать поток, он просто сидит на synchronized(entityLock)
внутри update()
Что действительно странно, что через некоторое время (между 2 секундами и минутой) PhysThread будет разблокирован, и игра будет продолжена, если ни один из потоков не будет заблокирован для более чем нескольких итераций циклов потока. (т.е. игра работает нормально)
Изменить: Я добавил в пример некоторые дополнительные материалы, на случай, если это является причиной проблемы. В основном, обновление и рисование массива объектов, а не одного объекта
В конце концов, я пошел на обманное решение. Я поместил блоки синхронизации вокруг доступа к массиву сущностей и поместил петли for внутри try/catch с помощью ArrayIndexOutOfBounds:
void update(){
try{
for(int i = 0;i<entities.length;i++){
GameEntity entity = entities[i];
synchrnonized(entity){
entity.updatePosition();
}
}
}catch(ArrayIndexOutOfBoundsException aioob){
if(tryAgain){
update();
} else {
return;
}
}
}
Проблема с этим решением состоит в том, что если entity.updateposition()
выбрасывает ArrayIndexOutOfBoundsException
из чего-то совершенно не связанного, то я поймаю его и неверно истолковал бы. Плюс все это немного беспорядочно, и раз в каждый момент кадр или обновление пропускаются
Как мне кажется, проблема решена, я подозреваю, что исходная причина, вероятно, лежит где-то глубже в моем коде, между входом в циклы for и фактическим изменением сущностей, и я действительно не думаю, что было бы справедливо сбрасывать мои весь код здесь.
Я оставлю вопрос без ответа на пару дней, если у кого-нибудь будет лучшее решение
Я думаю, что проблема здесь, вероятно, в том, что "синхронизированный" блок не гарантирует справедливости.
Поток OpenGL всегда будет отображаться непрерывно, поэтому он попытается повторно войти в onDraw, как только он закончит его. Поскольку выбор того, какой поток разрешен для входа в синхронизированный блок, является произвольным, возможно, что поток OpenGL пытается восстановить блокировку до того, как она будет выпущена в физический поток, и на основании некоторых произвольных критериев ей предоставляется блокировка и далее, не допуская ввода физического потока.
Это может объяснить, почему это происходит когда-то, а не другие, поскольку это произвольное решение.
Вы можете попробовать реализовать справедливую блокировку вместо блока синхронизации или сделать ее такой, чтобы OpenGL не пыталась перерисовать сцену более одного раза с момента последнего обновления физики (поместите поток визуализации в режим сна до тех пор, пока не произойдет обновление).