Skip to content

Commit

Permalink
feat(grid): pass getHex() function to all traversers, this way they c…
Browse files Browse the repository at this point in the history
…an use the cache

Also refactored the Grid's run() and traverse() methods: run() is simple again and traverse() is the
only method that sets the cache (although map() and neighborOf() should too). The impact on
performance is about 1% slower compared to a traverse that doesn't use caching at all. When multiple
traverse() calls are made the performance seems to increase up to ~25%(!)
  • Loading branch information
flauwekeul committed Apr 22, 2021
1 parent f519448 commit 64ec33b
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 55 deletions.
45 changes: 18 additions & 27 deletions src/grid/grid.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CompassDirection } from '../compass'
import { createHex, equals, Hex, HexCoordinates } from '../hex'
import { createHex, Hex, HexCoordinates } from '../hex'
import { HexCache } from '../hexCache'
import { neighborOf } from './functions'
import { rectangle } from './traversers'
import { RectangleOptions, Traverser } from './types'
import { rectangle, RectangleOptions } from './traversers'
import { GetOrCreateHexFn, Traverser } from './types'
import { forEach, map } from './utils'

// todo: add from() static method that only accepts hexes and creates a grid by picking the prototype and traverser (traverser is just: `() => hexes`)
Expand All @@ -14,6 +14,7 @@ export class Grid<T extends Hex> {

constructor(
public hexPrototype: T,
// todo: default to a no-op cache that does nothing?
public hexes = new HexCache<T>(),
private traverser: InternalTraverser<T> = infiniteTraverser,
) {}
Expand Down Expand Up @@ -41,12 +42,7 @@ export class Grid<T extends Hex> {

// todo: alias to take or takeUntil?
run(stopFn: (hex: T) => boolean = () => false) {
// when traverser() is called once, `this.hexes` will be empty and it needs to be set here,
// when traverser() is called more than once, `this.hexes` will already be set and must not be overridden by the last traversal,
// for the first traversal sets how many hexes the grid contains.
const hexes = [...this.traverser()]
this.hexes.items = this.hexes.size > 0 ? this.hexes.items : hexes
for (const hex of this.hexes.items) {
for (const hex of this.traverser()) {
if (stopFn(hex)) {
return this
}
Expand All @@ -67,38 +63,33 @@ export class Grid<T extends Hex> {
return this
}

const traverse: InternalTraverser<T> = () => {
const nextTraverse: InternalTraverser<T> = () => {
const result: T[] = []
const hasTraversedBefore = this.traverser !== infiniteTraverser
let previousHexes: T[] = []

if (this.hexes.size > 0) {
// use cached hexes
previousHexes = this.hexes.items
} else {
previousHexes = [...this.traverser()]
if (previousHexes.length > 0) {
// cache hexes
this.hexes.items = previousHexes
}
}

let cursor: T = previousHexes[previousHexes.length - 1] || createHex(this.hexPrototype).copy() // copy to enable users to make custom hexes
// run any previous traversal to set cache
this.traverser()
// todo: private method/property?
const getOrCreateHex: GetOrCreateHexFn<T> = (coordinates) =>
// todo: use Map for faster finding (also for `this.hexes.items.some()`)?
this.hexes.items.find((hex) => hex.equals(coordinates)) ?? createHex(this.hexPrototype).copy(coordinates) // copy to enable users to make custom hexes
let cursor: T = this.hexes.items[this.hexes.items.length - 1] || createHex(this.hexPrototype).copy() // copy to enable users to make custom hexes

for (const traverser of traversers) {
for (const nextCursor of traverser(cursor)) {
for (const nextCursor of traverser(cursor, getOrCreateHex)) {
cursor = nextCursor
if (hasTraversedBefore && !previousHexes.some((prevCoords) => equals(prevCoords, cursor))) {
if (hasTraversedBefore && !this.hexes.items.some((prevHex) => prevHex.equals(cursor))) {
return result // todo: or continue? or make this configurable?
}
result.push(cursor)
}
}

// cache hexes
this.hexes.items = result
return result
}

return this.copy(traverse)
return this.copy(nextTraverse)
}

// todo: maybe remove this method?
Expand Down
2 changes: 1 addition & 1 deletion src/grid/traversers/at.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Hex, HexCoordinates } from '../../hex'
import { Traverser } from '../types'

export const at = <T extends Hex>(coordinates: HexCoordinates): Traverser<T> => (cursor) => [cursor.copy(coordinates)]
export const at = <T extends Hex>(coordinates: HexCoordinates): Traverser<T> => (_, getHex) => [getHex(coordinates)]

export const start = at
15 changes: 9 additions & 6 deletions src/grid/traversers/branch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ import { Hex } from '../../hex'
import { Traverser } from '../types'

/**
* For each hex from `source` traverses over hexes from `traverser`
* For each hex from `source` traverses over hex coordinates from `traverser`
* @param source Each hex in the source is passed one-by-one as a cursor to the traverser
* @param traverser Receives each hex from source as the start cursor
* @param traverser Receives each hex coordinates from source as the start cursor
*/
// todo: maybe has some duplication with concat()
export const branch = <T extends Hex>(source: Traverser<T>, traverser: Traverser<T>): Traverser<T> => (cursor) => {
export const branch = <T extends Hex>(source: Traverser<T>, traverser: Traverser<T>): Traverser<T> => (
cursor,
getHex,
) => {
const result: T[] = []
let _cursor = cursor

for (const sourceCursor of source(_cursor)) {
_cursor = sourceCursor
for (const sourceCursor of source(_cursor, getHex)) {
_cursor = getHex(sourceCursor)
result.push(_cursor)
for (const branchCursor of traverser(sourceCursor)) {
for (const branchCursor of traverser(_cursor, getHex)) {
result.push(branchCursor)
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/grid/traversers/concat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { Traverser } from '../types'
*
* @param traversers One or more traversers to be combined into a single new traverser
*/
export const concat = <T extends Hex>(...traversers: Traverser<T>[]): Traverser<T> => (cursor) => {
export const concat = <T extends Hex>(...traversers: Traverser<T>[]): Traverser<T> => (cursor, getHex) => {
const result: T[] = []
let _cursor = cursor

for (const traverser of traversers) {
for (const nextCursor of traverser(_cursor)) {
_cursor = nextCursor
for (const nextCursor of traverser(_cursor, getHex)) {
_cursor = getHex(nextCursor)
result.push(_cursor)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/grid/traversers/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { neighborOf } from '../functions'
import { Traverser } from '../types'

export const move = <T extends Hex>(direction: CompassDirection, times = 1): Traverser<T> => {
return (cursor) => {
return (cursor, getHex) => {
const result: T[] = []
let _cursor = cursor

for (let i = 1; i <= times; i++) {
_cursor = _cursor.copy(neighborOf(_cursor, direction))
_cursor = getHex(neighborOf(_cursor, direction))
result.push(_cursor)
}

Expand Down
19 changes: 13 additions & 6 deletions src/grid/traversers/rectangle.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Compass, CompassDirection } from '../../compass'
import { Hex, HexCoordinates, hexToOffset } from '../../hex'
import { RectangleOptions, Traverser } from '../types'
import { Traverser } from '../types'
import { at } from './at'
import { branch } from './branch'
import { concat } from './concat'
Expand All @@ -14,18 +14,25 @@ export function rectangle<T extends Hex>(
optionsOrCornerA: RectangleOptions | HexCoordinates,
cornerB?: HexCoordinates,
): Traverser<T> {
return (cursor) => {
return (cursor, getHex) => {
const { width, height, start = { q: 0, r: 0 }, direction = CompassDirection.E } = cornerB
? optionsFromOpposingCorners(optionsOrCornerA as HexCoordinates, cornerB, cursor.isPointy, cursor.offset)
: (optionsOrCornerA as RectangleOptions)

return branch<T>(
concat(at(start), move(Compass.rotate(direction, 2), height - 1)),
move(direction, width - 1),
)(cursor)
return branch<T>(concat(at(start), move(Compass.rotate(direction, 2), height - 1)), move(direction, width - 1))(
cursor,
getHex,
)
}
}

export interface RectangleOptions {
width: number
height: number
start?: HexCoordinates
direction?: CompassDirection
}

function optionsFromOpposingCorners(
cornerA: HexCoordinates,
cornerB: HexCoordinates,
Expand Down
9 changes: 6 additions & 3 deletions src/grid/traversers/repeat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import { Hex } from '../../hex'
import { Traverser } from '../types'

// todo: looks a lot like Grid.traverse()
export const repeat = <T extends Hex>(amount: number, ...traversers: Traverser<T>[]): Traverser<T> => (cursor) => {
export const repeat = <T extends Hex>(amount: number, ...traversers: Traverser<T>[]): Traverser<T> => (
cursor,
getHex,
) => {
const result: T[] = []
let _cursor = cursor

for (let i = 0; i < amount; i++) {
for (const traverser of traversers) {
for (const nextCursor of traverser(_cursor)) {
_cursor = nextCursor
for (const nextCursor of traverser(_cursor, getHex)) {
_cursor = getHex(nextCursor)
result.push(_cursor)
}
}
Expand Down
10 changes: 3 additions & 7 deletions src/grid/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { CompassDirection } from '../compass'
import { Hex, HexCoordinates } from '../hex'

export interface Traverser<T extends Hex> {
(cursor: T): Iterable<T>
(cursor: T, getHex: GetOrCreateHexFn<T>): Iterable<T>
}

export interface RectangleOptions {
width: number
height: number
start?: HexCoordinates
direction?: CompassDirection
export interface GetOrCreateHexFn<T extends Hex> {
(coordinates: HexCoordinates): T
}
1 change: 1 addition & 0 deletions src/hexCache/hexCache.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Hex } from '../hex'

export interface Cache<T> {
// todo: rename to value?
items: T[]
size: number
}
Expand Down

0 comments on commit 64ec33b

Please sign in to comment.