Игры для iOS и управление Run-Loop
Во-первых, мой вопрос: как вы управляете своим iOS Run-Loop?
Следующая моя причина: я исследовал это с различными прототипами (против ранней стадии разработки) и обнаружил ряд недоумений.
- Во-первых, проблемы ввода и цикл выполнения заставляют меня попробовать следующее:
- при использовании наиболее рекомендуемой системы (CADisplayLink) я заметил, что некоторые сенсорные входы сбрасываются, как только загрузка процессора заставляет переворот буфера (presentRenderBuffer) ждать кадра. Это происходит только на устройстве, а не в симуляторе (досадно - похоже, это связано с ожиданием блокировки vsync в главном потоке и с тем, как процесс запуска цикла приложения касается ввода и приема сообщений)
- при использовании следующей наиболее рекомендуемой системы (NSTimer) я заметил, что некоторые сенсорные входы сбрасываются, когда нагрузка на процессор достигает определенной точки в симуляторе, но не в устройстве (что также раздражает). NSTimer также значительно снижает точность при запуске моих обновлений
- при использовании наименее рекомендуемой системы (выполнение цикла выполнения в собственном потоке, управляемом изнутри с помощью высокоточного таймера, созданного из mach_absolute_time, все мои проблемы с сенсорным вводом исчезают, однако мой код ASSERT теперь перехватывает неверный поток и только если я использую после программного прерывания. (Мой код подтверждения аналогичен http://iphone.m20.nl/wp/?p=1). Мне очень нравится, когда мой код подтверждения сразу появляется на той строке, которая вызвала проблему, поэтому это решение не совсем работоспособен для меня: сложнее отлаживать.
- Во-вторых, потерянное время:
- исследуя систему, я обнаружил, что независимо от частоты кадров (странно, но я полагаю, что статистически это все еще имеет смысл с vsync), я жду приблизительно 22% времени на vsync. Я подтвердил это, перемещаясь по glFlush / glFinish и играя с тем, как часто я делаю вызовы presentRenderBuffer. Это ключевой момент, когда я хотел бы обрабатывать ИИ и т. Д., А не просто останавливать блокирующий вызов gl. Единственный способ обдумать это - перенести рендеринг в собственный поток, но я не уверен, стоит ли начинать ре-архитектуру многопоточности на однопроцессорном устройстве.
Так кто-нибудь нашел волшебную пулю вокруг этих проблем? У кого-нибудь есть убийственная архитектура цикла запуска, которая на этой платформе обалденная? На данный момент, похоже, мне нужно выбрать меньшее из зол.
1 ответ
Для своих собственных проектов iOS, я использую классический подход (создать окно.nib, создать наследование класса EAGLView
, добавлять EAGLView
к представлению в контроллере представления, который помещен в его собственный.nib).
На работе я использовал немного другой подход, основанный на SDL, который вы можете проверить в нашей библиотеке с открытым исходным кодом, APRIL. Главная цель APRIL - поддержка как можно большего количества платформ, сохраняя при этом простоту (только управление окнами и вводом), а также ясность в вопросах лицензирования и бесплатное использование. Наши разработчики хотят писать приложения на одной платформе (Windows, Mac или Linux, в зависимости от вкусов и желаний), а затем код передается мне для адаптации к другим платформам.
В подходе, который мы используем в АПРЕЛЕ, вы не создаете.nibs, а после вызова UIApplicationMain
Вы указываете класс делегата в качестве четвертого аргумента. Основной код игры остается абсолютно одинаковым для каждой платформы, и только для этой платформы #ifdef
Занимался кодом или абстрагировался в вспомогательной библиотеке.
В делегате приложения вы создаете контроллер представления и окно:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// create a window.
// early creation so Default.png can be displayed while we're waiting for
// game initialization
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// viewcontroller will automatically add imageview
viewController = [[AprilViewController alloc] initWithWindow:window];
[viewController loadView];
// set window color
[window setBackgroundColor:[UIColor blackColor]];
// display the window
[window makeKeyAndVisible];
// thanks to Kyle Poole for this trick
// also used in latest SDL
// quote:
// KP: using a selector gets around the "failed to launch application in time" if the startup code takes too long
// This is easy to see if running with Valgrind
[self performSelector:@selector(runMain:) withObject:nil afterDelay:0.2f];
}
Заметьте, как мы задерживаем запуск на 0,2? Вот почему я упоминаю вид изображения выше. В течение этих 0,2 секунд у нас сразу после Default.png будет отображаться пустой экран, и перед переносом элемента управления в runMain: вводится дополнительная задержка, которая освобождает элемент управления для основного приложения:
- (void)runMain:(id)sender
{
// thanks to Kyle Poole for this trick
char *argv[] = {"april_ios"};
int status = april_RealMain (1, argv); //gArgc, gArgv);
#pragma unused(status)
}
Таким образом, теперь управление никогда не возвращается обратно в реальный основной цикл UIApplication. Затем вы создаете свой собственный основной цикл.
void iOSWindow::enterMainLoop()
{
while (mRunning)
{
// parse UIKit events
doEvents();
handleDisplayAndUpdate();
}
}
void iOSWindow::doEvents()
{
SInt32 result;
do {
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE);
} while(result == kCFRunLoopRunHandledSource);
}
(Кстати, контроллер представления используется, конечно, для упрощения поворота пользовательского интерфейса в соответствии с ориентацией устройства.)
Оба эти подхода используют CADisplayLink
если поддерживается ОС. Я не заметил никаких проблем ни с одним из методов, хотя мои частные проекты в основном основаны на акселерометре. Я подозреваю, что подход АПРЕЛЯ может убрать и некоторые проблемы.