Skip to content

Commit

Permalink
Implement caching on a system-depth basis (cache points)
Browse files Browse the repository at this point in the history
  • Loading branch information
mk12 committed Sep 8, 2015
1 parent 5a89ff1 commit 2cc6d71
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cache/
4 changes: 4 additions & 0 deletions clear-cache.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

cd "$(dirname "$0")"
rm cache/*.svg
3 changes: 1 addition & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,13 @@ func mainHandler(w http.ResponseWriter, req *http.Request) {
}

params := parseParams(req.URL)
log.Printf("Rendering %+v\n", params)
svg, err := renderSVG(params)
if err != nil {
fail(w, err.Error())
return
}

log.Printf("Rendering %+v\n", params)

if params.onlySVG {
w.Header().Set("Content-Type", "image/svg+xml")
io.WriteString(w, svg)
Expand Down
15 changes: 13 additions & 2 deletions system/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,29 @@ import "math"
// Named returns the system with the given name, or nil if it doesn't exist.
func Named(name string) *System {
if sys, ok := namedSystems[name]; ok {
return &sys
return sys
}
return nil
}

// name returns the name of the system, if it is an instance in namedSystems.
// Otherwise, returns the empty string.
func (s *System) name() string {
for key, sys := range namedSystems {
if s == sys {
return key
}
}
return ""
}

// MaxDepth returns the maximum depth the system should be rendered at.
func (s *System) MaxDepth() int {
return s.max - s.min
}

// namedSystems contains some common Lindenmayer systems.
var namedSystems = map[string]System{
var namedSystems = map[string]*System{
"koch": {
axiom: []byte("F++F++F"),
rules: rewriteSet{
Expand Down
20 changes: 10 additions & 10 deletions system/svg.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Options struct {

// SVG renders the system as a curve, returning a string of SVG data.
func (s *System) SVG(opts *Options) string {
segments := s.render(s.min + opts.Depth)
segments := s.cacheRender(s.min + opts.Depth)
view := calcViewBox(segments, opts)
thickness := opts.Thickness * math.Max(view.w, view.h) / StepFactor
p := fmtPrecision(opts.Precision)
Expand All @@ -37,7 +37,7 @@ func (s *System) SVG(opts *Options) string {
for _, points := range segments {
buf.WriteString("<polyline points='")
for _, pt := range points {
fmt.Fprintf(&buf, "%s,%s ", p(pt.x), p(pt.y))
fmt.Fprintf(&buf, "%s,%s ", p(pt.X), p(pt.Y))
}
buf.WriteString("'/>")
}
Expand All @@ -59,15 +59,15 @@ func calcViewBox(segments [][]vector, opts *Options) viewBox {
var xMin, xMax, yMin, yMax float64
for _, points := range segments {
for _, pt := range points {
if pt.x < xMin {
xMin = pt.x
} else if pt.x > xMax {
xMax = pt.x
if pt.X < xMin {
xMin = pt.X
} else if pt.X > xMax {
xMax = pt.X
}
if pt.y < yMin {
yMin = pt.y
} else if pt.y > yMax {
yMax = pt.y
if pt.Y < yMin {
yMin = pt.Y
} else if pt.Y > yMax {
yMax = pt.Y
}
}
}
Expand Down
48 changes: 48 additions & 0 deletions system/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ package system

import (
"bytes"
"encoding/gob"
"fmt"
"log"
"os"
"unicode"
)

Expand All @@ -23,6 +27,50 @@ type System struct {
max int // Maximum depth of recursion.
}

// cacheRender is like render but caches results to disk for better performance.
func (s *System) cacheRender(depth int) [][]vector {
name := s.name()
if name == "" {
return s.render(depth)
}

filename := fmt.Sprintf("cache/%s-%d.gob", name, depth)
_, err := os.Stat(filename)
if err == nil {
log.Println("Cache hit:", filename)
file, err := os.Open(filename)
if err != nil {
log.Println("File open error:", err)
return s.render(depth)
}
defer file.Close()

var segments [][]vector
err = gob.NewDecoder(file).Decode(&segments)
if err != nil {
log.Println("Gob decode error:", err)
return s.render(depth)
}
return segments
}
if os.IsNotExist(err) {
log.Println("Cache miss:", filename)
segments := s.render(depth)
file, err := os.Create(filename)
if err != nil {
log.Println("File create error:", err)
return segments
}
defer file.Close()

gob.NewEncoder(file).Encode(segments)
return segments
}

log.Println("File stat error:", err)
return s.render(depth)
}

// render draws the curve for the system at the given depth of recursion. It
// returns a list of polygonal lines as a 2D slice of points.
func (s *System) render(depth int) [][]vector {
Expand Down
10 changes: 5 additions & 5 deletions system/turtle.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "math"

// A vector is a two-dimensional Euclidean vector with x and y components.
type vector struct {
x, y float64
X, Y float64
}

// A turtle is a friendly animal that controls drawing.
Expand Down Expand Up @@ -40,8 +40,8 @@ func (s *System) initialTurtle(depth int) turtle {

// advance moves the turtle forward by one step.
func (t *turtle) advance() {
t.pos.x += t.vel.x
t.pos.y -= t.vel.y
t.pos.X += t.vel.X
t.pos.Y -= t.vel.Y
}

// turnCCW turns the turtle counterclockwise by one step.
Expand All @@ -57,6 +57,6 @@ func (t *turtle) turnCW() {
// rotate changes the turtle's direction by delta (in radians).
func (t *turtle) rotate(delta float64) {
t.dir += delta
t.vel.x = t.step * math.Cos(t.dir)
t.vel.y = t.step * math.Sin(t.dir)
t.vel.X = t.step * math.Cos(t.dir)
t.vel.Y = t.step * math.Sin(t.dir)
}

0 comments on commit 2cc6d71

Please sign in to comment.