После многих часов отладки и анализа мне наконец удалось выделить причину состояния гонки. Решение это другое дело!
Чтобы увидеть состояние гонки в действии, я записал видео в процессе отладки. С тех пор я углубил свое понимание ситуации, поэтому, пожалуйста, прости плохой комментарий и глупые механизмы, реализованные в процессе отладки.
http://screencast.com/t/aTAk1NOVanjR
Итак, ситуация: у нас есть реализация поверхности с двойной буферизацией (т. Е. Java.awt.Frame или Window), где есть непрерывный поток, который по существу непрерывно зацикливается, вызывая процесс рендеринга (который выполняет макет пользовательского интерфейса и отображает его). в буфер), а затем после рендеринга перетаскивает визуализированную область из буферного пространства в экран.
Вот версия псевдокода (строка полной версии 824 из Surface.java ) рендеринга с двойной буферизацией:
public RenderedRegions render() {
// pseudo code
RenderedRegions r = super.render();
if (r==null) // nothing rendered
return
for (region in r)
establish max bounds
blit(max bounds)
return r;
}
Как и в любой другой реализации AWT-поверхности, она также реализует (строка 507 в AWT.java - ограничение канала: ( - использовать ссылку Surface.java, замените core / Surface.java на plat / AWT.java ) переопределения рисования / обновления, которые также переносятся из заднего буфера на экран:
public void paint(Graphics gr) {
Rectangle r = gr.getClipBounds();
refreshFromBackbuffer(r.x - leftInset, r.y - topInset, r.width, r.height);
}
Блиттинг реализован (строка 371 в AWT.java) с использованием функции drawImage ():
/** synchronized as otherwise it is possible to blit before images have been rendered to the backbuffer */
public synchronized void blit(PixelBuffer s, int sx, int sy, int dx, int dy, int dx2, int dy2) {
discoverInsets();
try {
window.getGraphics().drawImage(((AWTPixelBuffer)s).i,
dx + leftInset, dy + topInset, // destination topleft corner
dx2 + leftInset, dy2 + topInset, // destination bottomright corner
sx, sy, // source topleft corner
sx + (dx2 - dx), sy + (dy2 - dy), // source bottomright corner
null);
} catch (NullPointerException npe) { /* FIXME: handle this gracefully */ }
}
(Внимание: именно здесь я начинаю делать предположения!)
Проблема здесь заключается в том, что drawImage является асинхронным и что первый вызов метода refreshBackBuffer () посредством рисования / обновления вызывается первым, а происходит секунд.
Итак ... блит уже синхронизирован. Очевидный способ предотвратить состояние гонки не работает. (
Пока я придумал два решения, но ни одно из них не идеально:
re-blit на следующем проходе рендера
минусы: снижение производительности, все еще немного мерцание из-за состояния гонки (действующий экран -> неверный экран -> действительный экран)
не копировать при рисовании / обновлении, вместо этого установите границы обновления и используйте эти границы при следующем проходе рендеринга
минусы: получить черный мерцание, когда экран становится недействительным и основной поток приложения догоняет
Здесь (1) представляется меньшим из двух зол. Редактировать: и (2) не работает, получая пустые экраны ... (1) работает нормально, но просто маскирует проблему, которая потенциально все еще существует.
То, на что я надеюсь, и, похоже, не могу придумать из-за моего слабого понимания синхронизированного и как его использовать, - это механизм блокировки, который каким-то образом учитывает асинхронный характер drawImage ().
Или, возможно, использовать ImageObserver?
Обратите внимание, что из-за характера приложения (Vexi, для тех, кто интересуется, веб-сайт устарел, и я могу использовать только 2 гиперссылки) поток рендеринга должен быть вне рисования / обновления - он имеет однопоточный скрипт Модель и процесс макета (подпроцесс рендеринга) запускает скрипт.