Skip to content
Permalink
Browse files

Optimization: Neighbor Pre-fetch (#10)

* use toroidal matrix for neighbor generation

* add settings to create larger grids
  • Loading branch information...
amiantos committed Jun 8, 2019
1 parent 0e78180 commit d83701a33fe4f3f52f43d061a376fbe75760b28e
@@ -135,6 +135,10 @@ final class ConfigureSheetController: NSObject {
}

switch manager.squareSize {
case .superSmall:
squareSizeControl.selectedSegment = 0
case .verySmall:
squareSizeControl.selectedSegment = 0
case .small:
squareSizeControl.selectedSegment = 0
case .medium:
@@ -199,6 +203,10 @@ final class ConfigureSheetController: NSObject {
squareSizeControl.selectedSegment = 1
case .large:
squareSizeControl.selectedSegment = 2
case .superSmall:
squareSizeControl.selectedSegment = 0
case .verySmall:
squareSizeControl.selectedSegment = 0
}
manager.setSquareSize(squareSize)
}
@@ -73,21 +73,30 @@ class LifeScene: SKScene {
private var allNodes: [LifeNode] = []
private var aliveNodes: [LifeNode] = []
private var livingNodeHistory: [Int] = []
private var lengthSquares: CGFloat = 16
private var heightSquares: CGFloat = 9
private var matrix: ToroidalMatrix<LifeNode> = ToroidalMatrix(rows: 0, columns: 0, defaultValue: LifeNode(relativePosition: .zero, alive: false, color: .black, size: .zero))

fileprivate func createLife() {
var lengthSquares: CGFloat = 16
var heightSquares: CGFloat = 9
switch squareSize {
case .large:
lengthSquares = 7
heightSquares = 4
case .small:
lengthSquares = 32
heightSquares = 18
case .verySmall:
lengthSquares = 64
heightSquares = 36
case .superSmall:
lengthSquares = 128
heightSquares = 74
default:
break
}

matrix = ToroidalMatrix(rows: Int(lengthSquares), columns: Int(heightSquares), defaultValue: LifeNode(relativePosition: .zero, alive: false, color: .black, size: .zero))

let totalSquares: CGFloat = lengthSquares * heightSquares
let squareWidth: CGFloat = size.width / lengthSquares
let squareHeight: CGFloat = size.height / heightSquares
@@ -117,6 +126,7 @@ class LifeScene: SKScene {
newSquare.color = aliveColors.randomElement()!
}
allNodes.append(newSquare)
matrix[Int(squareRelativePosition.x), Int(squareRelativePosition.y)] = newSquare

createdSquares += 1

@@ -131,61 +141,19 @@ class LifeScene: SKScene {
}
}

// Calculate Neighbors
// Pre-fetch Neighbors
for node in allNodes {
let neighbors = allNodes.filter {
let delta = (abs(node.relativePosition.x - $0.relativePosition.x), abs(node.relativePosition.y - $0.relativePosition.y))
switch delta {
case (1, 1), (1, 0), (0, 1):
return true
default:
return false
}
}
var neighbors: [LifeNode] = []
neighbors.append(matrix[Int(node.relativePosition.x - 1), Int(node.relativePosition.y)])
neighbors.append(matrix[Int(node.relativePosition.x + 1), Int(node.relativePosition.y)])
neighbors.append(matrix[Int(node.relativePosition.x), Int(node.relativePosition.y + 1)])
neighbors.append(matrix[Int(node.relativePosition.x), Int(node.relativePosition.y - 1)])
neighbors.append(matrix[Int(node.relativePosition.x + 1), Int(node.relativePosition.y + 1)])
neighbors.append(matrix[Int(node.relativePosition.x - 1), Int(node.relativePosition.y - 1)])
neighbors.append(matrix[Int(node.relativePosition.x - 1), Int(node.relativePosition.y + 1)])
neighbors.append(matrix[Int(node.relativePosition.x + 1), Int(node.relativePosition.y - 1)])
node.neighbors = neighbors
}

// Setup loop from edge
let maxX = lengthSquares - 1
let maxY = heightSquares - 1
let edgeNodes = allNodes.filter { [0, maxX].contains($0.relativePosition.x) || [0, maxY].contains($0.relativePosition.y) }
for node in edgeNodes {
var neighborPoints: [CGPoint] = []
switch node.relativePosition {
case CGPoint(x: 0, y: 0):
neighborPoints.append(CGPoint(x: maxX, y: maxY))
case CGPoint(x: maxX, y: maxY):
neighborPoints.append(CGPoint(x: 0, y: 0))
case CGPoint(x: 0, y: maxY):
neighborPoints.append(CGPoint(x: maxX, y: 0))
case CGPoint(x: maxX, y: 0):
neighborPoints.append(CGPoint(x: 0, y: maxY))
default:
break
}
if node.relativePosition.x == 0 {
neighborPoints.append(CGPoint(x: maxX, y: node.relativePosition.y - 1))
neighborPoints.append(CGPoint(x: maxX, y: node.relativePosition.y))
neighborPoints.append(CGPoint(x: maxX, y: node.relativePosition.y + 1))
}
if node.relativePosition.y == 0 {
neighborPoints.append(CGPoint(x: node.relativePosition.x - 1, y: maxY))
neighborPoints.append(CGPoint(x: node.relativePosition.x, y: maxY))
neighborPoints.append(CGPoint(x: node.relativePosition.x + 1, y: maxY))
}
if node.relativePosition.x == maxX {
neighborPoints.append(CGPoint(x: 0, y: node.relativePosition.y - 1))
neighborPoints.append(CGPoint(x: 0, y: node.relativePosition.y))
neighborPoints.append(CGPoint(x: 0, y: node.relativePosition.y + 1))
}
if node.relativePosition.y == maxY {
neighborPoints.append(CGPoint(x: node.relativePosition.x - 1, y: 0))
neighborPoints.append(CGPoint(x: node.relativePosition.x, y: 0))
neighborPoints.append(CGPoint(x: node.relativePosition.x + 1, y: 0))
}
let newNeighbors = edgeNodes.filter { neighborPoints.contains($0.relativePosition) }
node.neighbors.append(contentsOf: newNeighbors)
}
}

fileprivate func updateLife() {
@@ -241,6 +209,10 @@ class LifeScene: SKScene {
fileprivate func createRandomShapes(_: inout [LifeNode], _ livingNodes: inout [LifeNode]) {
var totalShapes: Int = 0
switch squareSize {
case .superSmall:
totalShapes = 500
case .verySmall:
totalShapes = 50
case .small:
totalShapes = 20
case .medium:
@@ -24,6 +24,8 @@ enum Appearance: Int {
}

enum SquareSize: Int {
case superSmall = -2
case verySmall = -1
case small = 0
case medium = 1
case large = 2
@@ -0,0 +1,37 @@
//
// ToroidalMatrix.swift
// Derived from dimo hamdy https://stackoverflow.com/a/53421491/2117288
// https://gist.github.com/amiantos/bb0f313da1ee686f4f69b8b44f3cd184
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
struct ToroidalMatrix<T> {
let rows: Int, columns: Int
var grid: [T]

init(rows: Int, columns: Int, defaultValue: T) {
self.rows = rows
self.columns = columns
grid = Array(repeating: defaultValue, count: rows * columns)
}

func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}

subscript(row: Int, column: Int) -> T {
get {
let safeRow = 0 ... rows-1 ~= row ? row : row > rows-1 ? 0 : row < 0 ? rows-1 : -1
let safeColumn = 0 ... columns-1 ~= column ? column : column > columns-1 ? 0 : column < 0 ? columns-1 : -1
assert(indexIsValid(row: safeRow, column: safeColumn), "Index out of range")
return grid[(safeRow * columns) + safeColumn]
}

set {
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
@@ -685,7 +685,7 @@
<objects>
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
<window key="window" title="Life Saver" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="1280" height="720"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
@@ -18,11 +18,10 @@ class ViewController: NSViewController {

let scene = LifeScene(size: view.frame.size)


scene.animationSpeed = .fast
scene.squareSize = .medium
scene.squareSize = .small

if let preset = lifePresets.filter({ $0.title == "Georgia" }).first {
if let preset = lifePresets.filter({ $0.title == "Santa Fe" }).first {
if let appearanceMode = preset.appearanceMode {
scene.appearanceMode = appearanceMode
}
@@ -31,9 +30,11 @@ class ViewController: NSViewController {
}
}


let skView = view as? SKView
skView?.presentScene(scene)
skView?.showsFPS = true
skView?.showsDrawCount = true
skView?.showsNodeCount = true

skView?.ignoresSiblingOrder = true
}
@@ -25,6 +25,9 @@
4480C7412294CFB800CC2EDC /* ConfigureSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4480C7402294CFB800CC2EDC /* ConfigureSheet.xib */; };
4480C7432294CFF400CC2EDC /* ConfigureSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4480C7422294CFF400CC2EDC /* ConfigureSheetController.swift */; };
44A547AD22936C6A0020EC8E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4FD88B12290553600AE066A /* Assets.xcassets */; };
B45BF8BB22AC33E600D77162 /* ToroidalMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = B45BF8BA22AC33E600D77162 /* ToroidalMatrix.swift */; };
B45BF8BC22AC33E900D77162 /* ToroidalMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = B45BF8BA22AC33E600D77162 /* ToroidalMatrix.swift */; };
B45BF8BD22AC33EA00D77162 /* ToroidalMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = B45BF8BA22AC33E600D77162 /* ToroidalMatrix.swift */; };
B49221622291F59D00D5DEA4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49221612291F59D00D5DEA4 /* AppDelegate.swift */; };
B492216A2291F59D00D5DEA4 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49221692291F59D00D5DEA4 /* GameViewController.swift */; };
B49221742291F6AA00D5DEA4 /* LifeScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4FD88BD229055FB00AE066A /* LifeScene.swift */; };
@@ -49,6 +52,7 @@
4480C7152294B60800CC2EDC /* LifeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifeManager.swift; sourceTree = "<group>"; };
4480C7402294CFB800CC2EDC /* ConfigureSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigureSheet.xib; sourceTree = "<group>"; };
4480C7422294CFF400CC2EDC /* ConfigureSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigureSheetController.swift; sourceTree = "<group>"; };
B45BF8BA22AC33E600D77162 /* ToroidalMatrix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToroidalMatrix.swift; sourceTree = "<group>"; };
B492215F2291F59C00D5DEA4 /* Life Saver tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Life Saver tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
B49221612291F59D00D5DEA4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
B49221692291F59D00D5DEA4 /* GameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = "<group>"; };
@@ -96,6 +100,7 @@
children = (
4480C7082294B05200CC2EDC /* FileGrabber.swift */,
4437905F22961BA200AC3AFF /* URLType.swift */,
B45BF8BA22AC33E600D77162 /* ToroidalMatrix.swift */,
);
path = Utilities;
sourceTree = "<group>";
@@ -322,6 +327,7 @@
4415352422974E220061434F /* LifeNode.swift in Sources */,
4480C70B2294B05200CC2EDC /* FileGrabber.swift in Sources */,
B492216A2291F59D00D5DEA4 /* GameViewController.swift in Sources */,
B45BF8BD22AC33EA00D77162 /* ToroidalMatrix.swift in Sources */,
B49F59B8229881C700571F05 /* LifeSettings.swift in Sources */,
B49221622291F59D00D5DEA4 /* AppDelegate.swift in Sources */,
);
@@ -337,6 +343,7 @@
4480C7092294B05200CC2EDC /* FileGrabber.swift in Sources */,
B4FD88B02290553400AE066A /* ViewController.swift in Sources */,
4480C70E2294B4C800CC2EDC /* LifeDatabase.swift in Sources */,
B45BF8BB22AC33E600D77162 /* ToroidalMatrix.swift in Sources */,
B4FD88A82290553400AE066A /* AppDelegate.swift in Sources */,
4415352022973A530061434F /* URLType.swift in Sources */,
4480C7162294B60800CC2EDC /* LifeManager.swift in Sources */,
@@ -350,6 +357,7 @@
files = (
B4FD88D32290BAB700AE066A /* LifeScene.swift in Sources */,
4415352322974E220061434F /* LifeNode.swift in Sources */,
B45BF8BC22AC33E900D77162 /* ToroidalMatrix.swift in Sources */,
4480C70A2294B05200CC2EDC /* FileGrabber.swift in Sources */,
4480C70F2294B4C800CC2EDC /* LifeDatabase.swift in Sources */,
4415351E229733C20061434F /* LifeSettings.swift in Sources */,

0 comments on commit d83701a

Please sign in to comment.
You can’t perform that action at this time.