Last active
September 14, 2016 07:24
-
-
Save magiconair/68a524fc847ba2860893a799f637f532 to your computer and use it in GitHub Desktop.
Port of the NaiveGame Java impl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a port of the naive Java version of | |
// https://jackmott.github.io/programming/2016/09/01/performance-in-the-large.html | |
// | |
// Code is not pretty!!! ;) | |
// | |
// Results: | |
// | |
// $ GODEBUG=gctrace=1 go run naive/main.go | |
// gc 1 @0.079s 0%: 0.11+0.30+0.077 ms clock, 0.34+0.008/0.40/0.48+0.23 ms cpu, 4->4->0 MB, 5 MB goal, 8 P | |
// # command-line-arguments | |
// gc 1 @0.004s 4%: 0.031+2.0+0.057 ms clock, 0.12+0/2.1/0.37+0.22 ms cpu, 4->4->3 MB, 5 MB goal, 8 P | |
// gc 2 @0.016s 5%: 0.006+1.6+0.60 ms clock, 0.048+0.061/1.4/2.5+4.8 ms cpu, 6->6->4 MB, 7 MB goal, 8 P | |
// # command-line-arguments | |
// gc 1 @0.001s 8%: 0.035+2.7+0.042 ms clock, 0.10+2.2/0.46/0.14+0.12 ms cpu, 4->4->4 MB, 5 MB goal, 8 P | |
// gc 2 @0.009s 6%: 0.023+2.6+0.082 ms clock, 0.16+0.28/2.4/0.35+0.57 ms cpu, 7->8->7 MB, 8 MB goal, 8 P | |
// gc 3 @0.025s 5%: 0.005+3.6+0.049 ms clock, 0.042+0.20/4.9/1.6+0.39 ms cpu, 13->14->13 MB, 15 MB goal, 8 P | |
// Loading World... | |
// gc 1 @0.000s 10%: 0.047+2.9+0.091 ms clock, 0.19+2.9/0.007/3.0+0.36 ms cpu, 4->4->4 MB, 5 MB goal, 8 P | |
// gc 2 @0.016s 4%: 0.032+2.1+0.063 ms clock, 0.22+0.028/2.0/0.11+0.44 ms cpu, 7->7->6 MB, 9 MB goal, 8 P | |
// gc 3 @0.026s 4%: 0.005+5.3+0.087 ms clock, 0.040+3.0/2.2/3.1+0.69 ms cpu, 15->15->15 MB, 16 MB goal, 8 P | |
// gc 4 @0.054s 4%: 0.025+7.6+0.039 ms clock, 0.20+4.5/5.5/4.5+0.31 ms cpu, 26->26->26 MB, 30 MB goal, 8 P | |
// gc 5 @0.110s 3%: 0.008+9.8+0.067 ms clock, 0.069+3.0/9.3/3.1+0.54 ms cpu, 48->49->47 MB, 52 MB goal, 8 P | |
// gc 6 @0.211s 2%: 0.008+13+0.045 ms clock, 0.068+0/14/6.9+0.36 ms cpu, 86->93->90 MB, 95 MB goal, 8 P | |
// gc 7 @0.387s 2%: 0.009+21+0.068 ms clock, 0.072+2.7/35/2.9+0.54 ms cpu, 170->170->166 MB, 181 MB goal, 8 P | |
// gc 8 @0.714s 2%: 0.021+36+0.075 ms clock, 0.17+2.9/64/3.4+0.60 ms cpu, 313->313->305 MB, 332 MB goal, 8 P | |
// gc 9 @1.353s 2%: 0.016+37+0.11 ms clock, 0.12+18/69/91+0.90 ms cpu, 593->601->585 MB, 611 MB goal, 8 P | |
// FINISHED! | |
// Load Time: 2.468865991s | |
// 1.096094ms | |
// 1.466952ms | |
// 3.008006ms | |
// 1.601441ms | |
// 1.025923ms | |
// ... | |
// gc 10 @20.295s 0%: 0.034+145+0.060 ms clock, 0.27+3.8/280/2.8+0.48 ms cpu, 1143->1143->1080 MB, 1171 MB | |
// | |
package main | |
import ( | |
"fmt" | |
"math" | |
"math/rand" | |
"time" | |
) | |
type Vector struct { | |
x, y, z float64 | |
} | |
func Mult(a, b Vector) Vector { | |
return Vector{a.x * b.x, a.y * b.y, a.z * b.z} | |
} | |
func Add(a, b Vector) Vector { | |
return Vector{a.x + b.x, a.y + b.y, a.z + b.z} | |
} | |
func Sub(a, b Vector) Vector { | |
return Vector{a.x - b.x, a.y - b.y, a.z - b.z} | |
} | |
func Dist(a, b Vector) float64 { | |
v := Sub(a, b) | |
return math.Sqrt(v.x*v.x + v.y*v.y + v.z*v.z) | |
} | |
type Block struct { | |
location Vector // x,y,z within the chunk | |
name string | |
durability int | |
textureid int | |
breakable bool | |
visible bool | |
typ int | |
} | |
type Type int | |
const ( | |
Zombie Type = iota | |
Chicken | |
Exploder | |
TallCreepyThing | |
) | |
type Entity struct { | |
r *rand.Rand | |
location Vector | |
name string | |
health int | |
speed Vector | |
typ Type | |
} | |
func NewEntity(location Vector, typ Type) *Entity { | |
e := &Entity{ | |
r: rand.New(rand.NewSource(time.Now().UnixNano())), | |
location: location, | |
typ: typ, | |
} | |
switch typ { | |
case Zombie: | |
e.name = "Zombie" | |
e.health = 50 | |
e.speed = Vector{0.5, 0.0, 0.5} //slow, can't fly | |
case Chicken: | |
e.name = "Chicken" | |
e.health = 25 | |
e.speed = Vector{0.75, 0.25, 0.75} //can fly a bit | |
case Exploder: | |
e.name = "Exploder" | |
e.health = 75 | |
e.speed = Vector{0.75, 0.0, 0.75} | |
case TallCreepyThing: | |
e.name = "Tall Creepy Thing" | |
e.health = 500 | |
e.speed = Vector{1.0, 1.0, 1.0} //does what he wants | |
} | |
return e | |
} | |
func (e *Entity) updatePosition() { | |
// Complex movement AI | |
rndUnitIshVector := Vector{1, 1, 1} | |
movementVector := Mult(rndUnitIshVector, e.speed) | |
e.location = Add(movementVector, e.location) | |
} | |
const NUM_BLOCKS = 65536 //just like minecraft! | |
const NUM_ENTITIES = 1000 | |
type Chunk struct { | |
blocks []Block | |
entities []*Entity | |
location Vector // x,y,z within world | |
} | |
func NewChunk(location Vector) *Chunk { | |
c := &Chunk{location: location} | |
// Preallocate the growable List because we are clever! | |
c.blocks = make([]Block, NUM_BLOCKS) | |
for i := range c.blocks { | |
c.blocks[i] = Block{ | |
location: Vector{float64(i), float64(i), float64(i)}, | |
name: fmt.Sprintf("Block:%d", i), | |
durability: 100, | |
textureid: 1, | |
breakable: true, | |
visible: true, | |
typ: 1, | |
} | |
} | |
c.entities = make([]*Entity, 0, NUM_ENTITIES) | |
for i := 0; i < NUM_ENTITIES/4; i++ { | |
// Fancy proc gen initial position equation | |
f := float64(i) | |
c.entities = append(c.entities, NewEntity(Vector{f, f, f}, Chicken)) | |
c.entities = append(c.entities, NewEntity(Vector{f + 2, f, f}, Zombie)) | |
c.entities = append(c.entities, NewEntity(Vector{f + 3, f, f}, Exploder)) | |
c.entities = append(c.entities, NewEntity(Vector{f + 4, f, f}, TallCreepyThing)) | |
} | |
return c | |
} | |
func (c *Chunk) processEntities() { | |
for _, e := range c.entities { | |
e.updatePosition() | |
} | |
} | |
const CHUNK_COUNT = 100 | |
type Game struct { | |
chunks []*Chunk | |
playerLocation Vector | |
chunkCounter int | |
} | |
func (g *Game) loadWorld() { | |
g.chunks = make([]*Chunk, CHUNK_COUNT) | |
for i := range g.chunks { | |
g.chunks[i] = NewChunk(Vector{float64(g.chunkCounter), 0, 0}) | |
g.chunkCounter++ | |
} | |
} | |
func (g *Game) updateChunks() { | |
chunks := []*Chunk{} | |
for _, c := range g.chunks { | |
c.processEntities() | |
chunkDistance := Dist(c.location, g.playerLocation) | |
if chunkDistance > CHUNK_COUNT { | |
chunks = append(chunks, NewChunk(Vector{float64(g.chunkCounter), 0, 0})) | |
g.chunkCounter++ | |
continue | |
} | |
chunks = append(chunks, c) | |
} | |
g.chunks = chunks | |
} | |
func main() { | |
game := &Game{} | |
fmt.Println("Loading World...") | |
start := time.Now() | |
game.loadWorld() | |
loadWorldTime := time.Since(start) | |
fmt.Println("FINISHED!") | |
fmt.Printf("Load Time: %s\n", loadWorldTime) | |
// Game Loop, you can never leave | |
for { | |
// check for dead entities | |
start = time.Now() | |
// mocking polling of the VR controller | |
playerMovement := Vector{0.1, 0, 0} | |
game.playerLocation = Add(game.playerLocation, playerMovement) | |
game.updateChunks() | |
t := time.Since(start) | |
fmt.Println(t) | |
// Lock it at 60FPS | |
if t < 16*time.Millisecond { | |
time.Sleep(16*time.Millisecond - t) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment