Когда я впервые открыл для себя ActionScript Workers , одним из первых, что я сделал, было использование их для реализации модели реакционной диффузионной системы Грея Скотта . Теперь, когда я начал играть со Swift, я думал, что сделаю нечто подобное с NSOperation .
Прежде чем мы начнем, большое предостережение: лучшее место для запуска больших клеточных автоматов, таких как системы реакционной диффузии, — это источник GPU. Этот пост действительно посвящен моим экспериментам с потоками Swift. Я с нетерпением жду, чтобы поработать с Металлом в ближайшее время, тогда у нас будет некоторая правильная реакция распространения продолжается.
Как бы я ни любил AS Workers, с ними немного сложно справиться: здесь много стандартного кода, и все данные, передаваемые между работниками, должны быть сериализованы. Классы, которые расширяют NSOperation, могут передавать любой тип данных назад и вперед в основной поток пользовательского интерфейса, и для их выполнения их просто необходимо добавить в очередь, и их завершение завершенияBlock вызывается по завершении:
let queue = NSOperationQueue(); [...] solver = GrayScottSolver(); solver.setGrayScott(grayScottData); solver.setParameterValues(f: f, k: k, dU: dU, dV: dV) solver.threadPriority = 0; solver.completionBlock = {self.didSolve(self.solver.getGrayScott())}; queue.addOperation(solver);
Однако, как мы увидим позже, я не использую блок завершения .
Я создал два класса операций, GrayScottSolver и GrayScottRenderer .
Класс решателя принимает одномерное представление двумерной сетки. Я начал с двумерного массива (из массива формы [x] [y] ), но обнаружил, что переключение обратно в одно измерение намного быстрее. Ее функция main () перебирает каждый элемент и создает временный массив по следующей формуле :
Первоначально я обновлял каждый элемент в массиве, но обнаружил добавление элементов во временный массив намного быстрее.
Класс рендерера принимает аналогичный массив, но создает экземпляр UIImage на основе данных. Для каждого пикселя я рисую прямоугольник 1 x 1 в графическом контексте, затем вызываю UIGraphicsGetImageFromCurrentImageContext () для заполнения UIImage. Здесь не требуется сериализация — основной поток пользовательского интерфейса может легко получить доступ к свойствам фонового потока без преобразования.
Виды ‘u’ системы отображаются на красный и зеленый, а виды ‘v’ на синий.
Я уже упоминал выше, что завершение блока выполняется после завершения операций. Однако этот блок завершения не выполняется в основном потоке пользовательского интерфейса. В результате, любые изменения пользовательского интерфейса не отражаются. Я пытаюсь установить свойство изображения UIImageView, когда рендерер завершен, и использование завершениеBlock не работает.
После большого количества царапин на голове у меня появилось немного хакерское решение: у меня таймер срабатывает двадцать раз в секунду, просматривая готовое свойство каждой операции. Если решатель завершен, он получает массив данных Грея Скотта, запускает операцию рендеринга и перезапускает решатель. Если операция рендеринга завершена, if устанавливает свойство изображения UIImageView — изображение, сгенерированное рендерером.
В любое время обе операции могут выполняться параллельно.
Чтобы добавить некоторые специальные функции для типов CGFloat и Int , я использовал расширения Swift . Расширения, также известные как ретроактивное моделирование, позволяют разработчикам добавлять новые методы в существующие классы. Например, чтобы сжать значения CGFloat между нулем и единицей , мой класс расширения так же прост:
func clip() -> CGFloat { if self < 0 { return 0.0; } else if self > 1.0 { return 1.0; } else { return self; } }
И теперь любой экземпляр
CGFloat может быть зафиксирован так:
let foo : CGFloat = 1234.5678; foo.clip()
Я добавил некоторые элементы управления для установки значений параметров, и они показывают, что, хотя каждый шаг занимает каждый решатель около трети секунды, поскольку они происходят в отдельном потоке, пользовательский интерфейс остается полностью отзывчивым.
Просто для забавы, если бы он работал на GPU, вот экранная запись этого проекта, действительно, очень ускоренная:
Проект доступен здесь, в моем репозитории GitHub . Если вы видите какие-либо способы ускорить процесс, я бы с удовольствием их услышал!